diff options
Diffstat (limited to 'src/direct_bt/BTManager.cpp')
-rw-r--r-- | src/direct_bt/BTManager.cpp | 1231 |
1 files changed, 1231 insertions, 0 deletions
diff --git a/src/direct_bt/BTManager.cpp b/src/direct_bt/BTManager.cpp new file mode 100644 index 00000000..2e8be6d7 --- /dev/null +++ b/src/direct_bt/BTManager.cpp @@ -0,0 +1,1231 @@ +/* + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2020 ZAFENA AB + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <BTAdapter.hpp> +#include <BTManager.hpp> +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <cstdio> + +#include <algorithm> + +// #define PERF_PRINT_ON 1 +// PERF3_PRINT_ON for close +// #define PERF3_PRINT_ON 1 +#include <jau/debug.hpp> + +#include <jau/basic_algos.hpp> + +#include "BTIoctl.hpp" + +#include "HCIIoctl.hpp" +#include "HCIComm.hpp" +#include "BTTypes1.hpp" +#include "SMPHandler.hpp" + +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <poll.h> + #include <signal.h> +} + +using namespace direct_bt; + +BTMode MgmtEnv::getEnvBTMode() { + // Environment variable is 'direct_bt.mgmt.btmode' or 'org.tinyb.btmode' + // Default is BTMode::LE, if non of the above environment variable is set. + std::string val = jau::environment::getProperty("direct_bt.mgmt.btmode"); + if( val.empty() ) { + val = jau::environment::getProperty("org.tinyb.btmode"); + } + const BTMode res = direct_bt::getBTMode(val); + return BTMode::NONE != res ? res : BTMode::DUAL; // fallback to default DUAL +} + +MgmtEnv::MgmtEnv() noexcept +: DEBUG_GLOBAL( jau::environment::get("direct_bt").debug ), + exploding( jau::environment::getExplodingProperties("direct_bt.mgmt") ), + MGMT_READER_THREAD_POLL_TIMEOUT( jau::environment::getInt32Property("direct_bt.mgmt.reader.timeout", 10000, 1500 /* min */, INT32_MAX /* max */) ), + MGMT_COMMAND_REPLY_TIMEOUT( jau::environment::getInt32Property("direct_bt.mgmt.cmd.timeout", 3000, 1500 /* min */, INT32_MAX /* max */) ), + MGMT_EVT_RING_CAPACITY( jau::environment::getInt32Property("direct_bt.mgmt.ringsize", 64, 64 /* min */, 1024 /* max */) ), + DEBUG_EVENT( jau::environment::getBooleanProperty("direct_bt.debug.mgmt.event", false) ), + DEFAULT_BTMODE( getEnvBTMode() ), + MGMT_READ_PACKET_MAX_RETRY( MGMT_EVT_RING_CAPACITY ) +{ +} + +const pid_t BTManager::pidSelf = getpid(); +std::mutex BTManager::mtx_singleton; + +void BTManager::mgmtReaderThreadImpl() noexcept { + { + const std::lock_guard<std::mutex> lock(mtx_mgmtReaderLifecycle); // RAII-style acquire and relinquish via destructor + mgmtReaderShallStop = false; + mgmtReaderRunning = true; + DBG_PRINT("DBTManager::reader: Started"); + cv_mgmtReaderInit.notify_all(); + } + thread_local jau::call_on_release thread_cleanup([&]() { + DBG_PRINT("DBTManager::mgmtReaderThreadCleanup: mgmtReaderRunning %d -> 0", mgmtReaderRunning.load()); + mgmtReaderRunning = false; + }); + + while( !mgmtReaderShallStop ) { + jau::snsize_t len; + if( !comm.isOpen() ) { + // not open + ERR_PRINT("DBTManager::reader: Not connected"); + mgmtReaderShallStop = true; + break; + } + + len = comm.read(rbuffer.get_wptr(), rbuffer.getSize(), env.MGMT_READER_THREAD_POLL_TIMEOUT); + if( 0 < len ) { + const jau::nsize_t len2 = static_cast<jau::nsize_t>(len); + const jau::nsize_t paramSize = len2 >= MGMT_HEADER_SIZE ? rbuffer.get_uint16_nc(4) : 0; + if( len2 < MGMT_HEADER_SIZE + paramSize ) { + WARN_PRINT("DBTManager::reader: length mismatch %zu < MGMT_HEADER_SIZE(%u) + %u", len2, MGMT_HEADER_SIZE, paramSize); + continue; // discard data + } + std::unique_ptr<MgmtEvent> event = MgmtEvent::getSpecialized(rbuffer.get_ptr(), len2); + const MgmtEvent::Opcode opc = event->getOpcode(); + if( MgmtEvent::Opcode::CMD_COMPLETE == opc || MgmtEvent::Opcode::CMD_STATUS == opc ) { + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV (CMD) %s", event->toString().c_str()); + if( mgmtEventRing.isFull() ) { + const jau::nsize_t dropCount = mgmtEventRing.capacity()/4; + mgmtEventRing.drop(dropCount); + WARN_PRINT("DBTManager-IO RECV Drop (%u oldest elements of %u capacity, ring full)", dropCount, mgmtEventRing.capacity()); + } + mgmtEventRing.putBlocking( std::move( event ) ); + } else if( MgmtEvent::Opcode::INDEX_ADDED == opc ) { + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV (ADD) %s", event->toString().c_str()); + std::thread adapterAddedThread(&BTManager::processAdapterAdded, this, std::move( event) ); // @suppress("Invalid arguments") + adapterAddedThread.detach(); + } else if( MgmtEvent::Opcode::INDEX_REMOVED == opc ) { + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV (REM) %s", event->toString().c_str()); + std::thread adapterRemovedThread(&BTManager::processAdapterRemoved, this, std::move( event ) ); // @suppress("Invalid arguments") + adapterRemovedThread.detach(); + } else { + // issue a callback + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV (CB) %s", event->toString().c_str()); + sendMgmtEvent( *event ); + } + } else if( ETIMEDOUT != errno && !mgmtReaderShallStop ) { // expected exits + ERR_PRINT("DBTManager::reader: HCIComm read error"); + } + } + { + const std::lock_guard<std::mutex> lock(mtx_mgmtReaderLifecycle); // RAII-style acquire and relinquish via destructor + WORDY_PRINT("DBTManager::reader: Ended. Ring has %u entries flushed", mgmtEventRing.getSize()); + mgmtEventRing.clear(); + mgmtReaderRunning = false; + cv_mgmtReaderInit.notify_all(); + } + + +} + +void BTManager::sendMgmtEvent(const MgmtEvent& event) noexcept { + const uint16_t dev_id = event.getDevID(); + MgmtAdapterEventCallbackList & mgmtEventCallbackList = mgmtAdapterEventCallbackLists[static_cast<uint16_t>(event.getOpcode())]; + int invokeCount = 0; + + jau::for_each_fidelity(mgmtEventCallbackList, [&](MgmtAdapterEventCallback &cb) { + if( 0 > cb.getDevID() || dev_id == cb.getDevID() ) { + try { + cb.getCallback().invoke(event); + } catch (std::exception &e) { + ERR_PRINT("DBTManager::sendMgmtEvent-CBs %d/%zd: MgmtAdapterEventCallback %s : Caught exception %s", + invokeCount+1, mgmtEventCallbackList.size(), + cb.toString().c_str(), e.what()); + } + invokeCount++; + } + }); + + COND_PRINT(env.DEBUG_EVENT, "DBTManager::sendMgmtEvent: Event %s -> %d/%zd callbacks", event.toString().c_str(), invokeCount, mgmtEventCallbackList.size()); + (void)invokeCount; +} + +static void mgmthandler_sigaction(int sig, siginfo_t *info, void *ucontext) noexcept { + bool pidMatch = info->si_pid == BTManager::pidSelf; + WORDY_PRINT("DBTManager.sigaction: sig %d, info[code %d, errno %d, signo %d, pid %d, uid %d, fd %d], pid-self %d (match %d)", + sig, info->si_code, info->si_errno, info->si_signo, + info->si_pid, info->si_uid, info->si_fd, + BTManager::pidSelf, pidMatch); + (void)ucontext; + + if( !pidMatch || SIGALRM != sig ) { + return; + } +#if 0 + // We do not de-install the handler on single use, + // as we act for multiple SIGALRM events within direct-bt + { + struct sigaction sa_setup; + bzero(&sa_setup, sizeof(sa_setup)); + sa_setup.sa_handler = SIG_DFL; + sigemptyset(&(sa_setup.sa_mask)); + sa_setup.sa_flags = 0; + if( 0 != sigaction( SIGALRM, &sa_setup, NULL ) ) { + ERR_PRINT("DBTManager.sigaction: Resetting sighandler"); + } + } +#endif +} + +bool BTManager::send(MgmtCommand &req) noexcept { + const std::lock_guard<std::recursive_mutex> lock(mtx_sendReply); // RAII-style acquire and relinquish via destructor + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO SENT %s", req.toString().c_str()); + TROOctets & pdu = req.getPDU(); + if ( comm.write( pdu.get_ptr(), pdu.getSize() ) < 0 ) { + ERR_PRINT("DBTManager::sendWithReply: HCIComm write error, req %s", req.toString().c_str()); + return false; + } + return true; +} + +std::unique_ptr<MgmtEvent> BTManager::sendWithReply(MgmtCommand &req) noexcept { + const std::lock_guard<std::recursive_mutex> lock(mtx_sendReply); // RAII-style acquire and relinquish via destructor + if( !send(req) ) { + return nullptr; + } + + // Ringbuffer read is thread safe + int32_t retryCount = 0; + while( retryCount < env.MGMT_READ_PACKET_MAX_RETRY ) { + std::unique_ptr<MgmtEvent> res = mgmtEventRing.getBlocking(env.MGMT_COMMAND_REPLY_TIMEOUT); + // std::unique_ptr<MgmtEvent> res = receiveNext(); + if( nullptr == res ) { + errno = ETIMEDOUT; + ERR_PRINT("DBTManager::sendWithReply.X: nullptr result (timeout -> abort): req %s", req.toString().c_str()); + return nullptr; + } else if( !res->validate(req) ) { + // This could occur due to an earlier timeout w/ a nullptr == res (see above), + // i.e. the pending reply processed here and naturally not-matching. + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV sendWithReply: res mismatch (drop evt, retryCount %d): res %s; req %s", + retryCount, res->toString().c_str(), req.toString().c_str()); + retryCount++; + } else { + COND_PRINT(env.DEBUG_EVENT, "DBTManager-IO RECV sendWithReply: res %s; req %s", res->toString().c_str(), req.toString().c_str()); + return res; + } + } + return nullptr; +} + +std::unique_ptr<AdapterInfo> BTManager::initAdapter(const uint16_t dev_id, const BTMode btMode) noexcept { + /** + * We weight on PairingMode::PASSKEY_ENTRY. FIXME: Have it configurable! + * + * BT Core Spec v5.2: Vol 3, Part H (SM): 2.3.5.1 Selecting key generation method Table 2.8 + * + * See SMPTypes.cpp: getPairingMode(const bool le_sc_pairing, const SMPIOCapability ioCap_init, const SMPIOCapability ioCap_resp) noexcept + */ +#if USE_LINUX_BT_SECURITY + const uint8_t debug_keys = 0; + const uint8_t ssp_on_param = 0x01; // SET_SSP 0x00 disabled, 0x01 enable Secure Simple Pairing. SSP only available for BREDR >= 2.1 not single-mode LE. + const uint8_t sc_on_param = 0x01; // SET_SECURE_CONN 0x00 disabled, 0x01 enables SC mixed, 0x02 enables SC only mode +#endif + + std::unique_ptr<AdapterInfo> adapterInfo(nullptr); // nullptr + AdapterSetting current_settings; + MgmtCommand req0(MgmtCommand::Opcode::READ_INFO, dev_id); + { + std::unique_ptr<MgmtEvent> res = sendWithReply(req0); + if( nullptr == res ) { + goto fail; + } + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getTotalSize() < MgmtEvtAdapterInfo::getRequiredTotalSize()) { + ERR_PRINT("Insufficient data for adapter info: req %d, res %s", MgmtEvtAdapterInfo::getRequiredTotalSize(), res->toString().c_str()); + goto fail; + } + const MgmtEvtAdapterInfo * res1 = static_cast<MgmtEvtAdapterInfo*>(res.get()); + adapterInfo = res1->toAdapterInfo(); + if( dev_id != adapterInfo->dev_id ) { + ABORT("AdapterInfo dev_id=%d != dev_id=%d: %s", adapterInfo->dev_id, dev_id, adapterInfo->toString().c_str()); + } + } + DBG_PRINT("initAdapter[%d, BTMode %s]: Start: %s", dev_id, getBTModeString(btMode).c_str(), adapterInfo->toString().c_str()); + current_settings = adapterInfo->getCurrentSettingMask(); + + setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, 0, current_settings); + + switch ( btMode ) { + case BTMode::DUAL: + setMode(dev_id, MgmtCommand::Opcode::SET_BREDR, 1, current_settings); + setDiscoverable(dev_id, 0, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_LE, 1, current_settings); +#if USE_LINUX_BT_SECURITY + setMode(dev_id, MgmtCommand::Opcode::SET_SECURE_CONN, sc_on_param, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SSP, ssp_on_param, current_settings); +#endif + break; + case BTMode::BREDR: + setMode(dev_id, MgmtCommand::Opcode::SET_BREDR, 1, current_settings); + setDiscoverable(dev_id, 0, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_LE, 0, current_settings); +#if USE_LINUX_BT_SECURITY + setMode(dev_id, MgmtCommand::Opcode::SET_SECURE_CONN, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SSP, ssp_on_param, current_settings); +#endif + break; + case BTMode::NONE: + [[fallthrough]]; // map NONE -> LE + case BTMode::LE: + setMode(dev_id, MgmtCommand::Opcode::SET_BREDR, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_LE, 1, current_settings); +#if USE_LINUX_BT_SECURITY + setMode(dev_id, MgmtCommand::Opcode::SET_SECURE_CONN, sc_on_param, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SSP, 0, current_settings); // SSP not available in LE single mode +#endif + break; + } + +#if USE_LINUX_BT_SECURITY + setMode(dev_id, MgmtCommand::Opcode::SET_DEBUG_KEYS, debug_keys, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_IO_CAPABILITY, direct_bt::number(defaultIOCapability), current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_BONDABLE, 1, current_settings); // required for pairing +#else + setMode(dev_id, MgmtCommand::Opcode::SET_SECURE_CONN, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SSP, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_DEBUG_KEYS, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_BONDABLE, 0, current_settings); +#endif + + setMode(dev_id, MgmtCommand::Opcode::SET_CONNECTABLE, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_FAST_CONNECTABLE, 0, current_settings); + + removeDeviceFromWhitelist(dev_id, BDAddressAndType::ANY_BREDR_DEVICE); // flush whitelist! + + setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, 1, current_settings); + + /** + * Update AdapterSettings post settings + */ + if( AdapterSetting::NONE != current_settings ) { + adapterInfo->setCurrentSettingMask(current_settings); + } else { + adapterInfo = nullptr; // flush + std::unique_ptr<MgmtEvent> res = sendWithReply(req0); + if( nullptr == res ) { + goto fail; + } + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getTotalSize() < MgmtEvtAdapterInfo::getRequiredTotalSize()) { + ERR_PRINT("Insufficient data for adapter info: req %d, res %s", MgmtEvtAdapterInfo::getRequiredTotalSize(), res->toString().c_str()); + goto fail; + } + const MgmtEvtAdapterInfo * res1 = static_cast<MgmtEvtAdapterInfo*>(res.get()); + adapterInfo = res1->toAdapterInfo(); + if( dev_id != adapterInfo->dev_id ) { + ABORT("AdapterInfo dev_id=%d != dev_id=%d: %s", adapterInfo->dev_id, dev_id, adapterInfo->toString().c_str()); + } + } + DBG_PRINT("initAdapter[%d, BTMode %s]: End: %s", dev_id, getBTModeString(btMode).c_str(), adapterInfo->toString().c_str()); + +fail: + return adapterInfo; +} + +void BTManager::shutdownAdapter(BTAdapter& adapter) noexcept { + DBG_PRINT("DBTManager::shutdownAdapter: %s", adapter.toString().c_str()); + const uint16_t dev_id = adapter.dev_id; + adapter.close(); // also issues removeMgmtEventCallback(dev_id); + + AdapterSetting current_settings; + setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, 0, current_settings); + + setMode(dev_id, MgmtCommand::Opcode::SET_BONDABLE, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_CONNECTABLE, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_FAST_CONNECTABLE, 0, current_settings); + + setMode(dev_id, MgmtCommand::Opcode::SET_DEBUG_KEYS, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_IO_CAPABILITY, direct_bt::number(SMPIOCapability::DISPLAY_ONLY), current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SSP, 0, current_settings); + setMode(dev_id, MgmtCommand::Opcode::SET_SECURE_CONN, 0, current_settings); + DBG_PRINT("DBTManager::shutdownAdapter: done: %s", adapter.toString().c_str()); +} + +BTManager::BTManager(const BTMode _defaultBTMode) noexcept +: env(MgmtEnv::get()), + defaultBTMode(BTMode::NONE != _defaultBTMode ? _defaultBTMode : env.DEFAULT_BTMODE), +#if USE_LINUX_BT_SECURITY + defaultIOCapability(SMPIOCapability::KEYBOARD_ONLY), +#else + defaultIOCapability(SMPIOCapability::UNSET), +#endif + rbuffer(ClientMaxMTU), comm(HCI_DEV_NONE, HCI_CHANNEL_CONTROL), + mgmtEventRing(env.MGMT_EVT_RING_CAPACITY), mgmtReaderShallStop(false), + mgmtReaderThreadId(0), mgmtReaderRunning(false), + allowClose( comm.isOpen() ) +{ + WORDY_PRINT("DBTManager.ctor: BTMode %s, pid %d", getBTModeString(defaultBTMode).c_str(), BTManager::pidSelf); + if( !allowClose ) { + ERR_PRINT("DBTManager::open: Could not open mgmt control channel"); + return; + } + + { + struct sigaction sa_setup; + bzero(&sa_setup, sizeof(sa_setup)); + sa_setup.sa_sigaction = mgmthandler_sigaction; + sigemptyset(&(sa_setup.sa_mask)); + sa_setup.sa_flags = SA_SIGINFO; + if( 0 != sigaction( SIGALRM, &sa_setup, NULL ) ) { + ERR_PRINT("DBTManager::ctor: Setting sighandler"); + } + } + { + std::unique_lock<std::mutex> lock(mtx_mgmtReaderLifecycle); // RAII-style acquire and relinquish via destructor + + std::thread mgmtReaderThread(&BTManager::mgmtReaderThreadImpl, this); // @suppress("Invalid arguments") + mgmtReaderThreadId = mgmtReaderThread.native_handle(); + // Avoid 'terminate called without an active exception' + // as hciReaderThreadImpl may end due to I/O errors. + mgmtReaderThread.detach(); + + while( false == mgmtReaderRunning ) { + cv_mgmtReaderInit.wait(lock); + } + } + + PERF_TS_T0(); + + // Mandatory + { + MgmtCommand req0(MgmtCommand::Opcode::READ_VERSION, MgmtConstU16::MGMT_INDEX_NONE); + std::unique_ptr<MgmtEvent> res = sendWithReply(req0); + if( nullptr == res ) { + goto fail; + } + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getDataSize() < 3) { + ERR_PRINT("Wrong version response: %s", res->toString().c_str()); + goto fail; + } + const uint8_t *data = res->getData(); + const uint8_t version = data[0]; + const uint16_t revision = jau::get_uint16(data, 1, true /* littleEndian */); + WORDY_PRINT("Bluetooth version %d.%d", version, revision); + if( version < 1 ) { + ERR_PRINT("Bluetooth version >= 1.0 required"); + goto fail; + } + } + // Optional + { + MgmtCommand req0(MgmtCommand::Opcode::READ_COMMANDS, MgmtConstU16::MGMT_INDEX_NONE); + std::unique_ptr<MgmtEvent> res = sendWithReply(req0); + if( nullptr == res ) { + goto next1; + } + if( MgmtEvent::Opcode::CMD_COMPLETE == res->getOpcode() && res->getDataSize() >= 4) { + const uint8_t *data = res->getData(); + const uint16_t num_commands = jau::get_uint16(data, 0, true /* littleEndian */); + const uint16_t num_events = jau::get_uint16(data, 2, true /* littleEndian */); + WORDY_PRINT("Bluetooth %d commands, %d events", num_commands, num_events); +#ifdef VERBOSE_ON + const int expDataSize = 4 + num_commands * 2 + num_events * 2; + if( res->getDataSize() >= expDataSize ) { + for(int i=0; i< num_commands; i++) { + const MgmtCommand::Opcode op = static_cast<MgmtCommand::Opcode>( get_uint16(data, 4+i*2, true /* littleEndian */) ); + DBG_PRINT("kernel op %d: %s", i, getMgmtOpcodeString(op).c_str()); + } + } +#endif + } + } + +next1: + // Mandatory + { + MgmtCommand req0(MgmtCommand::Opcode::READ_INDEX_LIST, MgmtConstU16::MGMT_INDEX_NONE); + std::unique_ptr<MgmtEvent> res = sendWithReply(req0); + if( nullptr == res ) { + goto fail; + } + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getDataSize() < 2) { + ERR_PRINT("Insufficient data for adapter index: res %s", res->toString().c_str()); + goto fail; + } + const uint8_t *data = res->getData(); + const uint16_t num_adapter = jau::get_uint16(data, 0, true /* littleEndian */); + WORDY_PRINT("Bluetooth %d adapter", num_adapter); + + const jau::nsize_t expDataSize = 2 + num_adapter * 2; + if( res->getDataSize() < expDataSize ) { + ERR_PRINT("Insufficient data for %d adapter indices: res %s", num_adapter, res->toString().c_str()); + goto fail; + } + for(int i=0; i < num_adapter; i++) { + const uint16_t dev_id = jau::get_uint16(data, 2+i*2, true /* littleEndian */); + std::unique_ptr<AdapterInfo> adapterInfo = initAdapter(dev_id, defaultBTMode); + if( nullptr != adapterInfo ) { + std::shared_ptr<BTAdapter> adapter = BTAdapter::make_shared(*this, *adapterInfo); + adapters.push_back( adapter ); + adapterIOCapability.push_back(defaultIOCapability); + DBG_PRINT("DBTManager::adapters %d/%d: dev_id %d: %s", i, num_adapter, dev_id, adapter->toString().c_str()); + } else { + DBG_PRINT("DBTManager::adapters %d/%d: dev_id %d: FAILED", i, num_adapter, dev_id); + } + } + } + + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_SETTINGS, jau::bindMemberFunc(this, &BTManager::mgmtEvNewSettingsCB)); + + if( jau::environment::get().debug ) { + addMgmtEventCallback(-1, MgmtEvent::Opcode::CONTROLLER_ERROR, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::CLASS_OF_DEV_CHANGED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_LINK_KEY, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_LONG_TERM_KEY, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_CONNECTED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_DISCONNECTED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::CONNECT_FAILED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::PIN_CODE_REQUEST, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::USER_CONFIRM_REQUEST, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::USER_PASSKEY_REQUEST, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::AUTH_FAILED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_FOUND, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DISCOVERING, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_BLOCKED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_UNBLOCKED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_UNPAIRED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::PASSKEY_NOTIFY, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_IRK, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_CSRK, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_WHITELIST_ADDED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::DEVICE_WHITELIST_REMOVED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::NEW_CONN_PARAM, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + + addMgmtEventCallback(-1, MgmtEvent::Opcode::LOCAL_OOB_DATA_UPDATED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + + addMgmtEventCallback(-1, MgmtEvent::Opcode::PAIR_DEVICE_COMPLETE, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::HCI_ENC_CHANGED, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::HCI_ENC_KEY_REFRESH_COMPLETE, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + addMgmtEventCallback(-1, MgmtEvent::Opcode::HCI_LE_REMOTE_USR_FEATURES, jau::bindMemberFunc(this, &BTManager::mgmtEventAnyCB)); + } + PERF_TS_TD("DBTManager::ctor.ok"); + DBG_PRINT("DBTManager::ctor: OK"); + return; + +fail: + close(); + PERF_TS_TD("DBTManager::ctor.fail"); + DBG_PRINT("DBTManager::ctor: FAIL"); + return; +} + +void BTManager::close() noexcept { + // Avoid disconnect re-entry -> potential deadlock + bool expConn = true; // C++11, exp as value since C++20 + if( !allowClose.compare_exchange_strong(expConn, false) ) { + // not open + DBG_PRINT("DBTManager::close: Not open"); + whitelist.clear(); + clearAllCallbacks(); + adapters.clear(); + adapterIOCapability.clear(); + comm.close(); + return; + } + PERF3_TS_T0(); + + const std::lock_guard<std::recursive_mutex> lock(mtx_sendReply); // RAII-style acquire and relinquish via destructor + DBG_PRINT("DBTManager::close: Start"); + removeAllDevicesFromWhitelist(); + clearAllCallbacks(); + + { + int i=0; + jau::for_each_fidelity(adapters, [&](std::shared_ptr<BTAdapter> & a) { + DBG_PRINT("DBTManager::close::shutdownAdapter: %d/%d processing: %s", i, adapters.size(), a->toString().c_str()); + shutdownAdapter(*a); + ++i; + }); + } + + adapters.clear(); + adapterIOCapability.clear(); + + // Interrupt DBTManager's HCIComm::read(..), avoiding prolonged hang + // and pull all underlying hci read operations! + comm.close(); + + PERF3_TS_TD("DBTManager::close.1"); + { + std::unique_lock<std::mutex> lockReader(mtx_mgmtReaderLifecycle); // RAII-style acquire and relinquish via destructor + const pthread_t tid_self = pthread_self(); + const pthread_t tid_reader = mgmtReaderThreadId; + mgmtReaderThreadId = 0; + const bool is_reader = tid_reader == tid_self; + DBG_PRINT("DBTManager::close: mgmtReader[running %d, shallStop %d, isReader %d, tid %p)", + mgmtReaderRunning.load(), mgmtReaderShallStop.load(), is_reader, (void*)tid_reader); + if( mgmtReaderRunning ) { + mgmtReaderShallStop = true; + if( !is_reader && 0 != tid_reader ) { + int kerr; + if( 0 != ( kerr = pthread_kill(tid_reader, SIGALRM) ) ) { + ERR_PRINT("DBTManager::close: pthread_kill %p FAILED: %d", (void*)tid_reader, kerr); + } + } + // Ensure the reader thread has ended, no runaway-thread using *this instance after destruction + while( true == mgmtReaderRunning ) { + cv_mgmtReaderInit.wait(lockReader); + } + } + } + PERF3_TS_TD("DBTManager::close.2"); + + { + struct sigaction sa_setup; + bzero(&sa_setup, sizeof(sa_setup)); + sa_setup.sa_handler = SIG_DFL; + sigemptyset(&(sa_setup.sa_mask)); + sa_setup.sa_flags = 0; + if( 0 != sigaction( SIGALRM, &sa_setup, NULL ) ) { + ERR_PRINT("DBTManager.sigaction: Resetting sighandler"); + } + } + + PERF3_TS_TD("DBTManager::close.X"); + DBG_PRINT("DBTManager::close: End"); +} + +std::shared_ptr<BTAdapter> BTManager::getDefaultAdapter() const noexcept { + typename adapters_t::const_iterator it = adapters.cbegin(); + for (; !it.is_end(); ++it) { + if( (*it)->isPowered() ) { + return *it; + } + } + return nullptr; +} + +std::shared_ptr<BTAdapter> BTManager::getAdapter(const EUI48 &mac) const noexcept { + typename adapters_t::const_iterator it = adapters.cbegin(); + for (; !it.is_end(); ++it) { + if ( (*it)->adapterInfo.address == mac ) { + return *it; + } + } + return nullptr; +} +std::shared_ptr<BTAdapter> BTManager::getAdapter(const uint16_t dev_id) const noexcept { + typename adapters_t::const_iterator it = adapters.cbegin(); + for (; !it.is_end(); ++it) { + if ( (*it)->dev_id == dev_id ) { + return *it; + } + } + return nullptr; +} + +std::shared_ptr<BTAdapter> BTManager::addAdapter(const AdapterInfo& ai ) noexcept { + typename adapters_t::iterator it = adapters.begin(); // lock mutex and copy_store + for (; !it.is_end(); ++it) { + if ( (*it)->dev_id == ai.dev_id ) { + break; + } + } + if( it.is_end() ) { + // new entry + std::shared_ptr<BTAdapter> adapter = BTAdapter::make_shared(*this, ai); + it.push_back( adapter ); + adapterIOCapability.push_back(defaultIOCapability); + DBG_PRINT("DBTManager::addAdapter: Adding new: %s", adapter->toString().c_str()) + it.write_back(); + return adapter; + } else { + // already existing + std::shared_ptr<BTAdapter> adapter = *it; + WARN_PRINT("DBTManager::addAdapter: Already existing %s, overwriting %s", ai.toString().c_str(), adapter->toString().c_str()) + adapter->adapterInfo = ai; + return adapter; + } +} + +std::shared_ptr<BTAdapter> BTManager::removeAdapter(const uint16_t dev_id) noexcept { + typename adapters_t::iterator it = adapters.begin(); // lock mutex and copy_store + for(; !it.is_end(); ++it ) { + std::shared_ptr<BTAdapter> & ai = *it; + if( ai->dev_id == dev_id ) { + adapterIOCapability.erase( adapterIOCapability.cbegin() + it.dist_begin() ); + std::shared_ptr<BTAdapter> res = ai; // copy + DBG_PRINT("DBTManager::removeAdapter: Remove: %s", res->toString().c_str()) + it.erase(); + it.write_back(); + return res; + } + } + DBG_PRINT("DBTManager::removeAdapter: Not found: dev_id %d", dev_id) + return nullptr; +} + +bool BTManager::removeAdapter(BTAdapter* adapter) noexcept { + typename adapters_t::iterator it = adapters.begin(); // lock mutex and copy_store + for(; !it.is_end(); ++it ) { + std::shared_ptr<BTAdapter> & ai = *it; + if( ai.get() == adapter ) { + adapterIOCapability.erase( adapterIOCapability.cbegin() + it.dist_begin() ); + DBG_PRINT("DBTManager::removeAdapter: Remove: %p -> %s", adapter, ai->toString().c_str()) + it.erase(); + it.write_back(); + return true; + } + } + DBG_PRINT("DBTManager::removeAdapter: Not found: %p", adapter) + return false; +} + +bool BTManager::setIOCapability(const uint16_t dev_id, const SMPIOCapability io_cap, SMPIOCapability& pre_io_cap) noexcept { + if( SMPIOCapability::UNSET != io_cap ) { +#if USE_LINUX_BT_SECURITY + typename adapters_t::const_iterator it = adapters.cbegin(); + for (; !it.is_end(); ++it) { + if( (*it)->dev_id == dev_id ) { + const typename adapters_t::difference_type index = it.dist_begin(); + const SMPIOCapability o = adapterIOCapability.at(index); + AdapterSetting current_settings { AdapterSetting::NONE }; // throw away return value, unchanged on SET_IO_CAPABILITY + if( setMode(dev_id, MgmtCommand::Opcode::SET_IO_CAPABILITY, direct_bt::number(io_cap), current_settings) ) { + adapterIOCapability.at(index) = io_cap; + pre_io_cap = o; + return true; + } else { + return false; + } + } + } +#endif + } + return false; +} + +SMPIOCapability BTManager::getIOCapability(const uint16_t dev_id) const noexcept { + typename adapters_t::const_iterator it = adapters.cbegin(); + for (; !it.is_end(); ++it) { + if( (*it)->dev_id == dev_id ) { + return adapterIOCapability.at( it.dist_begin() ); + } + } + return SMPIOCapability::UNSET; +} + +bool BTManager::setMode(const uint16_t dev_id, const MgmtCommand::Opcode opc, const uint8_t mode, AdapterSetting& current_settings) noexcept { + MgmtUint8Cmd req(opc, dev_id, mode); + std::unique_ptr<MgmtEvent> reply = sendWithReply(req); + MgmtStatus res; + if( nullptr != reply ) { + if( reply->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &reply1 = *static_cast<const MgmtEvtCmdComplete *>(reply.get()); + res = reply1.getStatus(); + if( MgmtStatus::SUCCESS == res ) { + reply1.getCurrentSettings(current_settings); + } + } else if( reply->getOpcode() == MgmtEvent::Opcode::CMD_STATUS ) { + const MgmtEvtCmdStatus &reply1 = *static_cast<const MgmtEvtCmdStatus *>(reply.get()); + res = reply1.getStatus(); + } else { + res = MgmtStatus::UNKNOWN_COMMAND; + } + } else { + res = MgmtStatus::TIMEOUT; + } + DBG_PRINT("DBTManager::setMode[%d, %s]: %s, result %s %s", dev_id, + MgmtCommand::getOpcodeString(opc).c_str(), jau::uint8HexString(mode).c_str(), + getMgmtStatusString(res).c_str(), getAdapterSettingMaskString(current_settings).c_str()); + return MgmtStatus::SUCCESS == res; +} + +MgmtStatus BTManager::setDiscoverable(const uint16_t dev_id, const uint8_t state, const uint16_t timeout_sec, AdapterSetting& current_settings) noexcept { + MgmtSetDiscoverableCmd req(dev_id, state, timeout_sec); + std::unique_ptr<MgmtEvent> reply = sendWithReply(req); + MgmtStatus res; + if( nullptr != reply ) { + if( reply->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &reply1 = *static_cast<const MgmtEvtCmdComplete *>(reply.get()); + res = reply1.getStatus(); + if( MgmtStatus::SUCCESS == res ) { + reply1.getCurrentSettings(current_settings); + } + } else if( reply->getOpcode() == MgmtEvent::Opcode::CMD_STATUS ) { + const MgmtEvtCmdStatus &reply1 = *static_cast<const MgmtEvtCmdStatus *>(reply.get()); + res = reply1.getStatus(); + } else { + res = MgmtStatus::UNKNOWN_COMMAND; + } + } else { + res = MgmtStatus::TIMEOUT; + } + DBG_PRINT("DBTManager::setDiscoverable[%d]: %s, result %s %s", dev_id, + req.toString().c_str(), getMgmtStatusString(res).c_str(), getAdapterSettingMaskString(current_settings).c_str()); + return res; +} + +ScanType BTManager::startDiscovery(const uint16_t dev_id, const BTMode btMode) noexcept { + return startDiscovery(dev_id, getScanType(btMode)); +} + +ScanType BTManager::startDiscovery(const uint16_t dev_id, const ScanType scanType) noexcept { + MgmtUint8Cmd req(MgmtCommand::Opcode::START_DISCOVERY, dev_id, number(scanType)); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + ScanType type = ScanType::NONE; + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() && 1 <= res1.getDataSize() ) { + const uint8_t *p = res1.getData(); + if( nullptr == p ) { // G++ 10: -Werror=null-dereference + ERR_PRINT("DBTManager::startDiscovery: Impossible MgmtEvtCmdComplete data nullptr: %s - %s", res1.toString().c_str(), req.toString().c_str()); + return type; + } + type = static_cast<ScanType>( p[0] ); + } + } + return type; +} +bool BTManager::stopDiscovery(const uint16_t dev_id, const ScanType type) noexcept { + MgmtUint8Cmd req(MgmtCommand::Opcode::STOP_DISCOVERY, dev_id, number(type)); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + return MgmtStatus::SUCCESS == res1.getStatus(); + } + return false; +} + +bool BTManager::uploadConnParam(const uint16_t dev_id, const BDAddressAndType & addressAndType, + const uint16_t conn_min_interval, const uint16_t conn_max_interval, + const uint16_t conn_latency, const uint16_t supervision_timeout) noexcept { + MgmtConnParam connParam{ addressAndType.address, addressAndType.type, conn_min_interval, conn_max_interval, conn_latency, supervision_timeout }; + MgmtLoadConnParamCmd req(dev_id, connParam); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + return MgmtStatus::SUCCESS == res1.getStatus(); + } + return false; +} + +MgmtStatus BTManager::uploadLinkKey(const uint16_t dev_id, const bool debug_keys, const MgmtLinkKeyInfo &key) noexcept { + MgmtLoadLinkKeyCmd req(dev_id, debug_keys, key); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + return res1.getStatus(); + } + return MgmtStatus::TIMEOUT; +} + +HCIStatusCode BTManager::uploadLongTermKey(const uint16_t dev_id, const MgmtLongTermKeyInfo &key) noexcept { + MgmtLoadLongTermKeyCmd req(dev_id, key); + HCIStatusCode res; + std::unique_ptr<MgmtEvent> reply = sendWithReply(req); + if( nullptr != reply ) { + if( reply->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + res = getHCIStatusCode( static_cast<const MgmtEvtCmdComplete *>(reply.get())->getStatus() ); + } else if( reply->getOpcode() == MgmtEvent::Opcode::CMD_STATUS ) { + res = getHCIStatusCode( static_cast<const MgmtEvtCmdStatus *>(reply.get())->getStatus() ); + } else { + res = HCIStatusCode::UNKNOWN_HCI_COMMAND; + } + } else { + res = HCIStatusCode::TIMEOUT; + } + DBG_PRINT("DBTManager::uploadLongTermKeyInfo[%d]: %s, result %s", dev_id, + req.toString().c_str(), getHCIStatusCodeString(res).c_str()); + return res; +} + +HCIStatusCode BTManager::uploadLongTermKeyInfo(const uint16_t dev_id, const BDAddressAndType & addressAndType, + const SMPLongTermKeyInfo& ltk) noexcept { + const MgmtLTKType key_type = getMgmtLTKType(ltk.properties); + const bool responder = ( SMPLongTermKeyInfo::Property::RESPONDER & ltk.properties ) != SMPLongTermKeyInfo::Property::NONE; + const MgmtLongTermKeyInfo mgmt_ltk_info { addressAndType.address, addressAndType.type, key_type, responder, ltk.enc_size, ltk.ediv, ltk.rand, ltk.ltk }; + MgmtLoadLongTermKeyCmd req(dev_id, mgmt_ltk_info); + HCIStatusCode res; + std::unique_ptr<MgmtEvent> reply = sendWithReply(req); + if( nullptr != reply ) { + if( reply->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + res = getHCIStatusCode( static_cast<const MgmtEvtCmdComplete *>(reply.get())->getStatus() ); + } else if( reply->getOpcode() == MgmtEvent::Opcode::CMD_STATUS ) { + res = getHCIStatusCode( static_cast<const MgmtEvtCmdStatus *>(reply.get())->getStatus() ); + } else { + res = HCIStatusCode::UNKNOWN_HCI_COMMAND; + } + } else { + res = HCIStatusCode::TIMEOUT; + } + DBG_PRINT("DBTManager::uploadLongTermKeyInfo[%d]: %s -> %s, result %s", dev_id, + ltk.toString().c_str(), req.toString().c_str(), getHCIStatusCodeString(res).c_str()); + return res; +} + +MgmtStatus BTManager::userPasskeyReply(const uint16_t dev_id, const BDAddressAndType & addressAndType, const uint32_t passkey) noexcept { + MgmtUserPasskeyReplyCmd cmd(dev_id, addressAndType, passkey); + std::unique_ptr<MgmtEvent> res = sendWithReply(cmd); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + // FIXME: Analyze address + addressType result? + return res1.getStatus(); + } + return MgmtStatus::TIMEOUT; +} + +MgmtStatus BTManager::userPasskeyNegativeReply(const uint16_t dev_id, const BDAddressAndType & addressAndType) noexcept { + MgmtUserPasskeyNegativeReplyCmd cmd(dev_id, addressAndType); + std::unique_ptr<MgmtEvent> res = sendWithReply(cmd); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + // FIXME: Analyze address + addressType result? + return res1.getStatus(); + } + return MgmtStatus::TIMEOUT; +} + +MgmtStatus BTManager::userConfirmReply(const uint16_t dev_id, const BDAddressAndType & addressAndType, const bool positive) noexcept { + std::unique_ptr<MgmtEvent> res; + if( positive ) { + MgmtUserConfirmReplyCmd cmd(dev_id, addressAndType); + res = sendWithReply(cmd); + } else { + MgmtUserConfirmNegativeReplyCmd cmd(dev_id, addressAndType); + res = sendWithReply(cmd); + } + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + // FIXME: Analyze address + addressType result? + return res1.getStatus(); + } + return MgmtStatus::TIMEOUT; +} + +bool BTManager::pairDevice(const uint16_t dev_id, const BDAddressAndType & addressAndType, const SMPIOCapability iocap) noexcept { + MgmtPairDeviceCmd cmd(dev_id, addressAndType, iocap); + return send(cmd); +} + +MgmtStatus BTManager::unpairDevice(const uint16_t dev_id, const BDAddressAndType & addressAndType, const bool disconnect) noexcept { + MgmtUnpairDeviceCmd cmd(dev_id, addressAndType, disconnect); + std::unique_ptr<MgmtEvent> res = sendWithReply(cmd); + + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + // FIXME: Analyze address + addressType result? + return res1.getStatus(); + } + return MgmtStatus::TIMEOUT; +} + +bool BTManager::isDeviceWhitelisted(const uint16_t dev_id, const BDAddressAndType & addressAndType) noexcept { + auto it = whitelist.cbegin(); + for( auto end = whitelist.cend(); it != end; ++it) { + std::shared_ptr<WhitelistElem> wle = *it; + if( wle->dev_id == dev_id && wle->address_and_type == addressAndType ) { + return true; + } + } + return false; +} + +bool BTManager::addDeviceToWhitelist(const uint16_t dev_id, const BDAddressAndType & addressAndType, const HCIWhitelistConnectType ctype) noexcept { + MgmtAddDeviceToWhitelistCmd req(dev_id, addressAndType, ctype); + + // Check if already exist in our local whitelist first, reject if so .. + if( isDeviceWhitelisted(dev_id, addressAndType) ) { + ERR_PRINT("DBTManager::addDeviceToWhitelist: Already in local whitelist, remove first: %s", req.toString().c_str()); + return false; + } + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() ) { + whitelist.push_back( std::make_shared<WhitelistElem>(dev_id, addressAndType, ctype) ); + return true; + } + } + return false; +} + +int BTManager::removeAllDevicesFromWhitelist() noexcept { +#if 0 + jau::darray<std::shared_ptr<WhitelistElem>> whitelist_copy = whitelist; + int count = 0; + DBG_PRINT("DBTManager::removeAllDevicesFromWhitelist.A: Start %zd elements", whitelist_copy.size()); + + for(auto it = whitelist_copy.cbegin(); it != whitelist_copy.cend(); ++it) { + std::shared_ptr<WhitelistElem> wle = *it; + removeDeviceFromWhitelist(wle->dev_id, wle->address, wle->address_type); + ++count; + } +#else + int count = 0; + DBG_PRINT("DBTManager::removeAllDevicesFromWhitelist.B: Start %d elements", count); + whitelist.clear(); + jau::for_each_const(adapters, [&](const std::shared_ptr<BTAdapter> & a) { + if( removeDeviceFromWhitelist(a->dev_id, BDAddressAndType::ANY_BREDR_DEVICE) ) { // flush whitelist! + ++count; + } + }); +#endif + + DBG_PRINT("DBTManager::removeAllDevicesFromWhitelist: End: Removed %d elements, remaining %zd elements", + count, whitelist.size()); + return count; +} + +bool BTManager::removeDeviceFromWhitelist(const uint16_t dev_id, const BDAddressAndType & addressAndType) noexcept { + // Remove from our local whitelist first + { + auto it = whitelist.cbegin(); + for( auto end = whitelist.cend(); it != end; ) { + std::shared_ptr<WhitelistElem> wle = *it; + if( wle->dev_id == dev_id && wle->address_and_type == addressAndType ) { + it = whitelist.erase(it); + } else { + ++it; + } + } + } + + // Actual removal + MgmtRemoveDeviceFromWhitelistCmd req(dev_id, addressAndType); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() ) { + return true; + } + } + return false; +} + +bool BTManager::disconnect(const bool ioErrorCause, + const uint16_t dev_id, const BDAddressAndType & addressAndType, + const HCIStatusCode reason) noexcept { + bool bres = false; + + // Always issue DISCONNECT command, even in case of an ioError (lost-connection), + // see Issue #124 fast re-connect on CSR adapter. + // This will always notify the adapter of a disconnected device. + { + MgmtDisconnectCmd req(dev_id, addressAndType); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() ) { + bres = true; + } + } + } + if( !ioErrorCause ) { + // In case of an ioError (lost-connection), don't wait for the lagging + // DISCONN_COMPLETE event but send it directly. + const MgmtEvtDeviceDisconnected e(dev_id, addressAndType, reason, 0xffff); + sendMgmtEvent(e); + } + return bres; +} + +std::shared_ptr<ConnectionInfo> BTManager::getConnectionInfo(const uint16_t dev_id, const BDAddressAndType& addressAndType) noexcept { + MgmtGetConnectionInfoCmd req(dev_id, addressAndType); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() ) { + std::shared_ptr<ConnectionInfo> result = res1.toConnectionInfo(); + return result; + } + } + return nullptr; +} + +std::shared_ptr<NameAndShortName> BTManager::setLocalName(const uint16_t dev_id, const std::string & name, const std::string & short_name) noexcept { + MgmtSetLocalNameCmd req (static_cast<uint16_t>(dev_id), name, short_name); + std::unique_ptr<MgmtEvent> res = sendWithReply(req); + if( nullptr != res && res->getOpcode() == MgmtEvent::Opcode::CMD_COMPLETE ) { + const MgmtEvtCmdComplete &res1 = *static_cast<const MgmtEvtCmdComplete *>(res.get()); + if( MgmtStatus::SUCCESS == res1.getStatus() ) { + std::shared_ptr<NameAndShortName> result = res1.toNameAndShortName(); + + // explicit LocalNameChanged event + MgmtEvtLocalNameChanged e(static_cast<uint16_t>(dev_id), result->getName(), result->getShortName()); + sendMgmtEvent(e); + return result; + } + } + return nullptr; +} + +/*** + * + * MgmtEventCallback section + * + */ + +static MgmtAdapterEventCallbackList::equal_comparator _mgmtAdapterEventCallbackEqComp_ID_CB = + [](const MgmtAdapterEventCallback &a, const MgmtAdapterEventCallback &b) -> bool { return a == b; }; + +static MgmtAdapterEventCallbackList::equal_comparator _mgmtAdapterEventCallbackEqComp_CB = + [](const MgmtAdapterEventCallback &a, const MgmtAdapterEventCallback &b) -> bool { return a.getCallback() == b.getCallback(); }; + +static MgmtAdapterEventCallbackList::equal_comparator _mgmtAdapterEventCallbackEqComp_ID = + [](const MgmtAdapterEventCallback &a, const MgmtAdapterEventCallback &b) -> bool { return a.getDevID() == b.getDevID(); }; + +bool BTManager::addMgmtEventCallback(const int dev_id, const MgmtEvent::Opcode opc, const MgmtEventCallback &cb) noexcept { + if( !isValidMgmtEventCallbackListsIndex(opc) ) { + ERR_PRINT("Opcode %s >= %d", MgmtEvent::getOpcodeString(opc).c_str(), mgmtAdapterEventCallbackLists.size()); + return false; + } + MgmtAdapterEventCallbackList &l = mgmtAdapterEventCallbackLists[static_cast<uint16_t>(opc)]; + /* const bool added = */ l.push_back_unique(MgmtAdapterEventCallback(dev_id, opc, cb), _mgmtAdapterEventCallbackEqComp_ID_CB); + return true; +} +int BTManager::removeMgmtEventCallback(const MgmtEvent::Opcode opc, const MgmtEventCallback &cb) noexcept { + if( !isValidMgmtEventCallbackListsIndex(opc) ) { + ERR_PRINT("Opcode %s >= %d", MgmtEvent::getOpcodeString(opc).c_str(), mgmtAdapterEventCallbackLists.size()); + return 0; + } + MgmtAdapterEventCallbackList &l = mgmtAdapterEventCallbackLists[static_cast<uint16_t>(opc)]; + return l.erase_matching( MgmtAdapterEventCallback( 0, MgmtEvent::Opcode::INVALID, cb ), + true /* all_matching */, _mgmtAdapterEventCallbackEqComp_CB); +} +int BTManager::removeMgmtEventCallback(const int dev_id) noexcept { + if( 0 > dev_id ) { + // skip dev_id -1 case, use clearAllMgmtEventCallbacks() here + return 0; + } + int count = 0; + for(size_t i=0; i<mgmtAdapterEventCallbackLists.size(); i++) { + MgmtAdapterEventCallbackList &l = mgmtAdapterEventCallbackLists[i]; + count += l.erase_matching( MgmtAdapterEventCallback( dev_id, MgmtEvent::Opcode::INVALID, MgmtEventCallback() ), + true /* all_matching */, _mgmtAdapterEventCallbackEqComp_ID); + } + return count; +} +void BTManager::clearMgmtEventCallbacks(const MgmtEvent::Opcode opc) noexcept { + if( !isValidMgmtEventCallbackListsIndex(opc) ) { + ERR_PRINT("Opcode %s >= %d", MgmtEvent::getOpcodeString(opc).c_str(), mgmtAdapterEventCallbackLists.size()); + return; + } + mgmtAdapterEventCallbackLists[static_cast<uint16_t>(opc)].clear(); +} +void BTManager::clearAllCallbacks() noexcept { + for(size_t i=0; i<mgmtAdapterEventCallbackLists.size(); i++) { + mgmtAdapterEventCallbackLists[i].clear(); + } + mgmtChangedAdapterSetCallbackList.clear(); +} + +void BTManager::processAdapterAdded(std::unique_ptr<MgmtEvent> e) noexcept { + const uint16_t dev_id = e->getDevID(); + + std::unique_ptr<AdapterInfo> adapterInfo = initAdapter(dev_id, defaultBTMode); + + if( nullptr != adapterInfo ) { + std::shared_ptr<BTAdapter> adapter = addAdapter( *adapterInfo ); + DBG_PRINT("DBTManager::Adapter[%d] Added: Start %s, added %d", dev_id, adapter->toString().c_str()); + sendMgmtEvent(*e); + DBG_PRINT("DBTManager::Adapter[%d] Added: User_ %s", dev_id, adapter->toString().c_str()); + jau::for_each_fidelity(mgmtChangedAdapterSetCallbackList, [&](ChangedAdapterSetCallback &cb) { + cb.invoke(true /* added */, adapter); + }); + DBG_PRINT("DBTManager::Adapter[%d] Added: End__ %s", dev_id, adapter->toString().c_str()); + } else { + DBG_PRINT("DBTManager::Adapter[%d] Added: InitAI failed", dev_id); + } +} +void BTManager::processAdapterRemoved(std::unique_ptr<MgmtEvent> e) noexcept { + const uint16_t dev_id = e->getDevID(); + std::shared_ptr<BTAdapter> ai = removeAdapter(dev_id); + if( nullptr != ai ) { + DBG_PRINT("DBTManager::Adapter[%d] Removed: Start: %s", dev_id, ai->toString().c_str()); + sendMgmtEvent(*e); + DBG_PRINT("DBTManager::Adapter[%d] Removed: User_: %s", dev_id, ai->toString().c_str()); + jau::for_each_fidelity(mgmtChangedAdapterSetCallbackList, [&](ChangedAdapterSetCallback &cb) { + cb.invoke(false /* added */, ai); + }); + ai->close(); // issuing dtor on DBTAdapter + DBG_PRINT("DBTManager::Adapter[%d] Removed: End__: %s", dev_id, ai->toString().c_str()); + } else { + DBG_PRINT("DBTManager::Adapter[%d] Removed: RemoveAI failed", dev_id); + } +} +bool BTManager::mgmtEvNewSettingsCB(const MgmtEvent& e) noexcept { + const MgmtEvtNewSettings &event = *static_cast<const MgmtEvtNewSettings *>(&e); + std::shared_ptr<BTAdapter> adapter = getAdapter(event.getDevID()); + if( nullptr != adapter ) { + const AdapterSetting old_settings = adapter->adapterInfo.getCurrentSettingMask(); + const AdapterSetting new_settings = adapter->adapterInfo.setCurrentSettingMask(event.getSettings()); + DBG_PRINT("DBTManager:mgmt:NewSettings: Adapter[%d] %s -> %s - %s", + event.getDevID(), + getAdapterSettingMaskString(old_settings).c_str(), + getAdapterSettingMaskString(new_settings).c_str(), + e.toString().c_str()); + } else { + DBG_PRINT("DBTManager:mgmt:NewSettings: Adapter[%d] %s -> adapter not present - %s", + event.getDevID(), + getAdapterSettingMaskString(event.getSettings()).c_str(), + e.toString().c_str()); + } + return true; +} + +bool BTManager::mgmtEventAnyCB(const MgmtEvent& e) noexcept { + DBG_PRINT("DBTManager:mgmt:Any: %s", e.toString().c_str()); + (void)e; + return true; +} + +/** + * ChangedAdapterSetCallback handling + */ + +static ChangedAdapterSetCallbackList::equal_comparator _changedAdapterSetCallbackEqComp = + [](const ChangedAdapterSetCallback& a, const ChangedAdapterSetCallback& b) -> bool { return a == b; }; + + +void BTManager::addChangedAdapterSetCallback(const ChangedAdapterSetCallback & l) { + ChangedAdapterSetCallback* l_p = const_cast<ChangedAdapterSetCallback*>(&l); + mgmtChangedAdapterSetCallbackList.push_back(l); + + jau::for_each_fidelity(adapters, [&](std::shared_ptr<BTAdapter>& ai) { + l_p->invoke(true /* added */, ai); + }); +} +int BTManager::removeChangedAdapterSetCallback(const ChangedAdapterSetCallback & l) { + return mgmtChangedAdapterSetCallbackList.erase_matching(l, true /* all_matching */, _changedAdapterSetCallbackEqComp); +} + +void BTManager::addChangedAdapterSetCallback(ChangedAdapterSetFunc f) { + addChangedAdapterSetCallback( + ChangedAdapterSetCallback( + jau::bindPlainFunc<bool, bool, std::shared_ptr<BTAdapter>&>(f) + ) ); +} +int BTManager::removeChangedAdapterSetCallback(ChangedAdapterSetFunc f) { + ChangedAdapterSetCallback l( jau::bindPlainFunc<bool, bool, std::shared_ptr<BTAdapter>&>(f) ); + return mgmtChangedAdapterSetCallbackList.erase_matching(l, true /* all_matching */, _changedAdapterSetCallbackEqComp); +} |