diff options
Diffstat (limited to 'src/direct_bt/BTAdapter.cpp')
-rw-r--r-- | src/direct_bt/BTAdapter.cpp | 1465 |
1 files changed, 1465 insertions, 0 deletions
diff --git a/src/direct_bt/BTAdapter.cpp b/src/direct_bt/BTAdapter.cpp new file mode 100644 index 00000000..2cf9bc03 --- /dev/null +++ b/src/direct_bt/BTAdapter.cpp @@ -0,0 +1,1465 @@ +/* + * 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> + +#include <jau/debug.hpp> + +#include <jau/basic_algos.hpp> + +#include "BTIoctl.hpp" +#include "HCIIoctl.hpp" +#include "HCIComm.hpp" + + +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <poll.h> +} + +using namespace direct_bt; + +std::shared_ptr<BTDevice> BTAdapter::findDevice(device_list_t & devices, const EUI48 & address, const BDAddressType addressType) noexcept { + const jau::nsize_t size = devices.size(); + for (jau::nsize_t i = 0; i < size; ++i) { + std::shared_ptr<BTDevice> & e = devices[i]; + if ( nullptr != e && address == e->getAddressAndType().address && addressType == e->getAddressAndType().type) { + return e; + } + } + return nullptr; +} + +std::shared_ptr<BTDevice> BTAdapter::findDevice(device_list_t & devices, BTDevice const & device) noexcept { + const jau::nsize_t size = devices.size(); + for (jau::nsize_t i = 0; i < size; ++i) { + std::shared_ptr<BTDevice> & e = devices[i]; + if ( nullptr != e && device == *e ) { + return e; + } + } + return nullptr; +} + +bool BTAdapter::addConnectedDevice(const std::shared_ptr<BTDevice> & device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_connectedDevices); // RAII-style acquire and relinquish via destructor + if( nullptr != findDevice(connectedDevices, *device) ) { + return false; + } + connectedDevices.push_back(device); + return true; +} + +bool BTAdapter::removeConnectedDevice(const BTDevice & device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_connectedDevices); // RAII-style acquire and relinquish via destructor + auto end = connectedDevices.end(); + for (auto it = connectedDevices.begin(); it != end; ++it) { + if ( nullptr != *it && device == **it ) { + connectedDevices.erase(it); + return true; + } + } + return false; +} + +int BTAdapter::disconnectAllDevices(const HCIStatusCode reason) noexcept { + device_list_t devices; + { + const std::lock_guard<std::mutex> lock(mtx_connectedDevices); // RAII-style acquire and relinquish via destructor + devices = connectedDevices; // copy! + } + const int count = devices.size(); + auto end = devices.end(); + for (auto it = devices.begin(); it != end; ++it) { + if( nullptr != *it ) { + (*it)->disconnect(reason); // will erase device from list via removeConnectedDevice(..) above + } + } + return count; +} + +std::shared_ptr<BTDevice> BTAdapter::findConnectedDevice (const EUI48 & address, const BDAddressType & addressType) noexcept { + const std::lock_guard<std::mutex> lock(mtx_connectedDevices); // RAII-style acquire and relinquish via destructor + return findDevice(connectedDevices, address, addressType); +} + +// ************************************************* +// ************************************************* +// ************************************************* + +bool BTAdapter::validateDevInfo() noexcept { + bool ok = false; + currentMetaScanType = ScanType::NONE; + keep_le_scan_alive = false; + + if( !mgmt.isOpen() ) { + ERR_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: Manager not open", dev_id); + goto errout0; + } + if( !hci.isOpen() ) { + ERR_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: HCIHandler closed", dev_id); + goto errout0; + } + + old_settings = adapterInfo.getCurrentSettingMask(); + + btMode = getBTMode(); + if( BTMode::NONE == btMode ) { + ERR_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: BTMode invalid, BREDR nor LE set: %s", dev_id, adapterInfo.toString().c_str()); + return false; + } + hci.setBTMode(btMode); + + if( adapterInfo.isCurrentSettingBitSet(AdapterSetting::POWERED) ) { + HCILocalVersion version; + HCIStatusCode status = hci.getLocalVersion(version); + if( HCIStatusCode::SUCCESS != status ) { + ERR_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: POWERED, LocalVersion failed %s - %s", + dev_id, getHCIStatusCodeString(status).c_str(), adapterInfo.toString().c_str()); + return false; + } else { + WORDY_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: POWERED, %s - %s", + dev_id, version.toString().c_str(), adapterInfo.toString().c_str()); + } + } else { + WORDY_PRINT("DBTAdapter::validateDevInfo: Adapter[%d]: Not POWERED: %s", dev_id, adapterInfo.toString().c_str()); + } + ok = true; + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::DISCOVERING, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceDiscoveringMgmt)) && ok; + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::NEW_SETTINGS, jau::bindMemberFunc(this, &BTAdapter::mgmtEvNewSettingsMgmt)) && ok; + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::LOCAL_NAME_CHANGED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvLocalNameChangedMgmt)) && ok; + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::PIN_CODE_REQUEST, jau::bindMemberFunc(this, &BTAdapter::mgmtEvPinCodeRequestMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::USER_CONFIRM_REQUEST, jau::bindMemberFunc(this, &BTAdapter::mgmtEvUserConfirmRequestMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::USER_PASSKEY_REQUEST, jau::bindMemberFunc(this, &BTAdapter::mgmtEvUserPasskeyRequestMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::AUTH_FAILED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvAuthFailedMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::DEVICE_UNPAIRED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceUnpairedMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::PAIR_DEVICE_COMPLETE, jau::bindMemberFunc(this, &BTAdapter::mgmtEvPairDeviceCompleteMgmt)); + ok = mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::NEW_LONG_TERM_KEY, jau::bindMemberFunc(this, &BTAdapter::mgmtEvNewLongTermKeyMgmt)); + + if( !ok ) { + ERR_PRINT("Could not add all required MgmtEventCallbacks to DBTManager: %s", toString().c_str()); + return false; + } + +#if 0 + mgmt.addMgmtEventCallback(dev_id, MgmtEvent::Opcode::DEVICE_DISCONNECTED, bindMemberFunc(this, &BTAdapter::mgmtEvDeviceDisconnectedMgmt)); +#endif + + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DISCOVERING, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceDiscoveringHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DEVICE_CONNECTED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceConnectedHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::CONNECT_FAILED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvConnectFailedHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DEVICE_DISCONNECTED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceDisconnectedHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DEVICE_FOUND, jau::bindMemberFunc(this, &BTAdapter::mgmtEvDeviceFoundHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_LE_REMOTE_USR_FEATURES, jau::bindMemberFunc(this, &BTAdapter::mgmtEvHCILERemoteUserFeaturesHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_ENC_CHANGED, jau::bindMemberFunc(this, &BTAdapter::mgmtEvHCIEncryptionChangedHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_ENC_KEY_REFRESH_COMPLETE, jau::bindMemberFunc(this, &BTAdapter::mgmtEvHCIEncryptionKeyRefreshCompleteHCI)) && ok; + + if( !ok ) { + ERR_PRINT("Could not add all required MgmtEventCallbacks to HCIHandler: %s of %s", hci.toString().c_str(), toString().c_str()); + return false; // dtor local HCIHandler w/ closing + } + hci.addSMPMsgCallback(jau::bindMemberFunc(this, &BTAdapter::hciSMPMsgCallback)); + + return true; + +errout0: + return false; +} + +BTAdapter::BTAdapter(const BTAdapter::ctor_cookie& cc, BTManager& mgmt_, const AdapterInfo& adapterInfo_) noexcept +: debug_event(jau::environment::getBooleanProperty("direct_bt.debug.adapter.event", false)), + debug_lock(jau::environment::getBooleanProperty("direct_bt.debug.adapter.lock", false)), + mgmt( mgmt_ ), + adapterInfo( adapterInfo_ ), + dev_id( adapterInfo.dev_id ), + hci( dev_id ) +{ + (void)cc; + valid = validateDevInfo(); +} + +BTAdapter::~BTAdapter() noexcept { + if( !isValid() ) { + DBG_PRINT("DBTAdapter::dtor: dev_id %d, invalid, %p", dev_id, this); + mgmt.removeAdapter(this); // remove this instance from manager + return; + } + DBG_PRINT("DBTAdapter::dtor: ... %p %s", this, toString().c_str()); + close(); + + mgmt.removeAdapter(this); // remove this instance from manager + + DBG_PRINT("DBTAdapter::dtor: XXX"); +} + +void BTAdapter::close() noexcept { + if( !isValid() ) { + // Native user app could have destroyed this instance already from + DBG_PRINT("DBTAdapter::close: dev_id %d, invalid, %p", dev_id, this); + return; + } + DBG_PRINT("DBTAdapter::close: ... %p %s", this, toString().c_str()); + keep_le_scan_alive = false; + + // mute all listener first + { + int count = mgmt.removeMgmtEventCallback(dev_id); + DBG_PRINT("DBTAdapter::close removeMgmtEventCallback: %d callbacks", count); + } + statusListenerList.clear(); + + poweredOff(); + + DBG_PRINT("DBTAdapter::close: closeHCI: ..."); + hci.close(); + DBG_PRINT("DBTAdapter::close: closeHCI: XXX"); + + { + const std::lock_guard<std::mutex> lock(mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + discoveredDevices.clear(); + } + { + const std::lock_guard<std::mutex> lock(mtx_connectedDevices); // RAII-style acquire and relinquish via destructor + connectedDevices.clear();; + } + { + const std::lock_guard<std::mutex> lock(mtx_sharedDevices); // RAII-style acquire and relinquish via destructor + sharedDevices.clear(); + } + valid = false; + DBG_PRINT("DBTAdapter::close: XXX"); +} + +void BTAdapter::poweredOff() noexcept { + if( !isValid() ) { + DBG_PRINT("DBTAdapter::poweredOff: dev_id %d, invalid, %p", dev_id, this); + return; + } + DBG_PRINT("DBTAdapter::poweredOff: ... %p %s", this, toString(false).c_str()); + keep_le_scan_alive = false; + + stopDiscovery(); + + // Removes all device references from the lists: connectedDevices, discoveredDevices, sharedDevices + disconnectAllDevices(); + removeDiscoveredDevices(); + + hci.setCurrentScanType(ScanType::NONE); + currentMetaScanType = ScanType::NONE; + + // ensure all hci states are reset. + hci.clearAllStates(); + + unlockConnectAny(); + + DBG_PRINT("DBTAdapter::poweredOff: XXX"); +} + +void BTAdapter::printSharedPtrListOfDevices() noexcept { + { + const std::lock_guard<std::mutex> lock0(mtx_sharedDevices); + jau::printSharedPtrList("SharedDevices", sharedDevices); + } + { + const std::lock_guard<std::mutex> lock0(mtx_discoveredDevices); + jau::printSharedPtrList("DiscoveredDevices", discoveredDevices); + } + { + const std::lock_guard<std::mutex> lock0(mtx_connectedDevices); + jau::printSharedPtrList("ConnectedDevices", connectedDevices); + } +} + +std::shared_ptr<NameAndShortName> BTAdapter::setLocalName(const std::string &name, const std::string &short_name) noexcept { + return mgmt.setLocalName(dev_id, name, short_name); +} + +bool BTAdapter::setDiscoverable(bool value) noexcept { + AdapterSetting current_settings { AdapterSetting::NONE } ; + return MgmtStatus::SUCCESS == mgmt.setDiscoverable(dev_id, value ? 0x01 : 0x00, 10 /* timeout seconds */, current_settings); +} + +bool BTAdapter::setBondable(bool value) noexcept { + AdapterSetting current_settings { AdapterSetting::NONE } ; + return mgmt.setMode(dev_id, MgmtCommand::Opcode::SET_BONDABLE, value ? 1 : 0, current_settings); +} + +bool BTAdapter::setPowered(bool value) noexcept { + AdapterSetting current_settings { AdapterSetting::NONE } ; + return mgmt.setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, value ? 1 : 0, current_settings); +} + +bool BTAdapter::lockConnect(const BTDevice & device, const bool wait, const SMPIOCapability io_cap) noexcept { + std::unique_lock<std::mutex> lock(mtx_single_conn_device); // RAII-style acquire and relinquish via destructor + const uint32_t timeout_ms = 10000; // FIXME: Configurable? + + if( nullptr != single_conn_device_ptr ) { + if( device == *single_conn_device_ptr ) { + COND_PRINT(debug_lock, "DBTAdapter::lockConnect: Success: Already locked, same device: %s", device.toString(false).c_str()); + return true; // already set, same device: OK, locked + } + if( wait ) { + while( nullptr != single_conn_device_ptr ) { + std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); + std::cv_status s = cv_single_conn_device.wait_until(lock, t0 + std::chrono::milliseconds(timeout_ms)); + if( std::cv_status::timeout == s && nullptr != single_conn_device_ptr ) { + if( debug_lock ) { + jau::PLAIN_PRINT(true, "DBTAdapter::lockConnect: Failed: Locked (waited)"); + jau::PLAIN_PRINT(true, " - locked-by-other-device %s", single_conn_device_ptr->toString(false).c_str()); + jau::PLAIN_PRINT(true, " - lock-failed-for %s", device.toString(false).c_str()); + } + return false; + } + } + // lock was released + } else { + if( debug_lock ) { + jau::PLAIN_PRINT(true, "DBTAdapter::lockConnect: Failed: Locked (no-wait)"); + jau::PLAIN_PRINT(true, " - locked-by-other-device %s", single_conn_device_ptr->toString(false).c_str()); + jau::PLAIN_PRINT(true, " - lock-failed-for %s", device.toString(false).c_str()); + } + return false; // already set, not waiting, blocked + } + } + single_conn_device_ptr = &device; + + if( SMPIOCapability::UNSET != io_cap ) { +#if USE_LINUX_BT_SECURITY + SMPIOCapability pre_io_cap { SMPIOCapability::UNSET }; + const bool res_iocap = mgmt.setIOCapability(dev_id, io_cap, pre_io_cap); + if( res_iocap ) { + iocap_defaultval = pre_io_cap; + COND_PRINT(debug_lock, "DBTAdapter::lockConnect: Success: New lock, setIOCapability[%s -> %s], %s", + getSMPIOCapabilityString(pre_io_cap).c_str(), getSMPIOCapabilityString(io_cap).c_str(), + device.toString(false).c_str()); + return true; + } else { + // failed, unlock and exit + COND_PRINT(debug_lock, "DBTAdapter::lockConnect: Failed: setIOCapability[%s], %s", + getSMPIOCapabilityString(io_cap).c_str(), device.toString(false).c_str()); + single_conn_device_ptr = nullptr; + cv_single_conn_device.notify_all(); // notify waiting getter + return false; + } +#else + COND_PRINT(debug_lock, "DBTAdapter::lockConnect: Success: New lock, ignored io-cap: %s, %s", + getSMPIOCapabilityString(io_cap).c_str() + device.toString(false).c_str()); + return true; +#endif + } else { + COND_PRINT(debug_lock, "DBTAdapter::lockConnect: Success: New lock, no io-cap: %s", device.toString(false).c_str()); + return true; + } +} + +bool BTAdapter::unlockConnect(const BTDevice & device) noexcept { + std::unique_lock<std::mutex> lock(mtx_single_conn_device); // RAII-style acquire and relinquish via destructor + + if( nullptr != single_conn_device_ptr && device == *single_conn_device_ptr ) { + const SMPIOCapability v = iocap_defaultval; + iocap_defaultval = SMPIOCapability::UNSET; + if( SMPIOCapability::UNSET != v ) { + // Unreachable: !USE_LINUX_BT_SECURITY + SMPIOCapability o; + const bool res = mgmt.setIOCapability(dev_id, v, o); + COND_PRINT(debug_lock, "DBTAdapter::unlockConnect: Success: setIOCapability[res %d: %s -> %s], %s", + res, getSMPIOCapabilityString(o).c_str(), getSMPIOCapabilityString(v).c_str(), + single_conn_device_ptr->toString(false).c_str()); + } else { + COND_PRINT(debug_lock, "DBTAdapter::unlockConnect: Success: %s", + single_conn_device_ptr->toString(false).c_str()); + } + single_conn_device_ptr = nullptr; + cv_single_conn_device.notify_all(); // notify waiting getter + return true; + } else { + if( debug_lock ) { + const std::string other_device_str = nullptr != single_conn_device_ptr ? single_conn_device_ptr->toString(false) : "null"; + jau::PLAIN_PRINT(true, "DBTAdapter::unlockConnect: Not locked:"); + jau::PLAIN_PRINT(true, " - locked-by-other-device %s", other_device_str.c_str()); + jau::PLAIN_PRINT(true, " - unlock-failed-for %s", device.toString(false).c_str()); + } + return false; + } +} + +bool BTAdapter::unlockConnectAny() noexcept { + std::unique_lock<std::mutex> lock(mtx_single_conn_device); // RAII-style acquire and relinquish via destructor + + if( nullptr != single_conn_device_ptr ) { + const SMPIOCapability v = iocap_defaultval; + iocap_defaultval = SMPIOCapability::UNSET; + if( SMPIOCapability::UNSET != v ) { + // Unreachable: !USE_LINUX_BT_SECURITY + SMPIOCapability o; + const bool res = mgmt.setIOCapability(dev_id, v, o); + COND_PRINT(debug_lock, "DBTAdapter::unlockConnectAny: Success: setIOCapability[res %d: %s -> %s]; %s", + res, getSMPIOCapabilityString(o).c_str(), getSMPIOCapabilityString(v).c_str(), + single_conn_device_ptr->toString(false).c_str()); + } else { + COND_PRINT(debug_lock, "DBTAdapter::unlockConnectAny: Success: %s", + single_conn_device_ptr->toString(false).c_str()); + } + single_conn_device_ptr = nullptr; + cv_single_conn_device.notify_all(); // notify waiting getter + return true; + } else { + iocap_defaultval = SMPIOCapability::UNSET; + COND_PRINT(debug_lock, "DBTAdapter::unlockConnectAny: Not locked"); + return false; + } +} + +HCIStatusCode BTAdapter::reset() noexcept { + if( !isValid() ) { + ERR_PRINT("DBTAdapter::reset(): Adapter invalid: %s, %s", aptrHexString(this).c_str(), toString().c_str()); + return HCIStatusCode::UNSPECIFIED_ERROR; + } + if( !hci.isOpen() ) { + ERR_PRINT("DBTAdapter::reset(): HCI closed: %s, %s", aptrHexString(this).c_str(), toString().c_str()); + return HCIStatusCode::UNSPECIFIED_ERROR; + } +#if 0 + const bool wasPowered = isPowered(); + // Adapter will be reset, close connections and cleanup off-thread. + preReset(); + + HCIStatusCode status = hci->reset(); + if( HCIStatusCode::SUCCESS != status ) { + ERR_PRINT("DBTAdapter::reset: reset failed: %s", getHCIStatusCodeString(status).c_str()); + } else if( wasPowered ) { + if( !setPowered(true) ) { + ERR_PRINT("DBTAdapter::reset: setPowered(true) failed"); + status = HCIStatusCode::UNSPECIFIED_ERROR; + } + } + return status; +#else + return hci.resetAdapter(); +#endif +} + +bool BTAdapter::isDeviceWhitelisted(const BDAddressAndType & addressAndType) noexcept { + return mgmt.isDeviceWhitelisted(dev_id, addressAndType); +} + +bool BTAdapter::addDeviceToWhitelist(const BDAddressAndType & addressAndType, const HCIWhitelistConnectType ctype, + const uint16_t conn_interval_min, const uint16_t conn_interval_max, + const uint16_t conn_latency, const uint16_t timeout) { + if( !isPowered() ) { + ERR_PRINT("DBTAdapter::startDiscovery: Adapter not powered: %s", toString().c_str()); + return false; + } + if( mgmt.isDeviceWhitelisted(dev_id, addressAndType) ) { + ERR_PRINT("DBTAdapter::addDeviceToWhitelist: device already listed: dev_id %d, address%s", dev_id, addressAndType.toString().c_str()); + return true; + } + + if( !mgmt.uploadConnParam(dev_id, addressAndType, conn_interval_min, conn_interval_max, conn_latency, timeout) ) { + ERR_PRINT("DBTAdapter::addDeviceToWhitelist: uploadConnParam(dev_id %d, address%s, interval[%u..%u], latency %u, timeout %u): Failed", + dev_id, addressAndType.toString().c_str(), conn_interval_min, conn_interval_max, conn_latency, timeout); + } + return mgmt.addDeviceToWhitelist(dev_id, addressAndType, ctype); +} + +bool BTAdapter::removeDeviceFromWhitelist(const BDAddressAndType & addressAndType) { + return mgmt.removeDeviceFromWhitelist(dev_id, addressAndType); +} + +static jau::cow_darray<std::shared_ptr<AdapterStatusListener>>::equal_comparator _adapterStatusListenerRefEqComparator = + [](const std::shared_ptr<AdapterStatusListener> &a, const std::shared_ptr<AdapterStatusListener> &b) -> bool { return *a == *b; }; + +bool BTAdapter::addStatusListener(std::shared_ptr<AdapterStatusListener> l) { + if( nullptr == l ) { + throw jau::IllegalArgumentException("DBTAdapterStatusListener ref is null", E_FILE_LINE); + } + const bool added = statusListenerList.push_back_unique(l, _adapterStatusListenerRefEqComparator); + if( added ) { + sendAdapterSettingsInitial(*l, jau::getCurrentMilliseconds()); + } + return true; +} + +bool BTAdapter::removeStatusListener(std::shared_ptr<AdapterStatusListener> l) { + if( nullptr == l ) { + throw jau::IllegalArgumentException("DBTAdapterStatusListener ref is null", E_FILE_LINE); + } + const int count = statusListenerList.erase_matching(l, false /* all_matching */, _adapterStatusListenerRefEqComparator); + return count > 0; +} + +bool BTAdapter::removeStatusListener(const AdapterStatusListener * l) { + if( nullptr == l ) { + throw jau::IllegalArgumentException("DBTAdapterStatusListener ref is null", E_FILE_LINE); + } + { + auto begin = statusListenerList.begin(); // lock mutex and copy_store + while ( !begin.is_end() ) { + if ( **begin == *l ) { + begin.erase(); + begin.write_back(); + return true; + } else { + ++begin; + } + } + } + return false; +} + +int BTAdapter::removeAllStatusListener() { + int count = statusListenerList.size(); + statusListenerList.clear(); + return count; +} + +void BTAdapter::checkDiscoveryState() noexcept { + const ScanType currentNativeScanType = hci.getCurrentScanType(); + // Check LE scan state + if( keep_le_scan_alive == false ) { + if( hasScanType(currentMetaScanType, ScanType::LE) != hasScanType(currentNativeScanType, ScanType::LE) ) { + std::string msg("Invalid DiscoveryState: keepAlive "+std::to_string(keep_le_scan_alive.load())+ + ", currentScanType*[native "+ + getScanTypeString(currentNativeScanType)+" != meta "+ + getScanTypeString(currentMetaScanType)+"]"); + ERR_PRINT(msg.c_str()); + // ABORT? + } + } else { + if( !hasScanType(currentMetaScanType, ScanType::LE) && hasScanType(currentNativeScanType, ScanType::LE) ) { + std::string msg("Invalid DiscoveryState: keepAlive "+std::to_string(keep_le_scan_alive.load())+ + ", currentScanType*[native "+ + getScanTypeString(currentNativeScanType)+", meta "+ + getScanTypeString(currentMetaScanType)+"]"); + ERR_PRINT(msg.c_str()); + // ABORT? + } + } +} + +HCIStatusCode BTAdapter::startDiscovery(const bool keepAlive, const HCILEOwnAddressType own_mac_type, + const uint16_t le_scan_interval, const uint16_t le_scan_window) +{ + // FIXME: Respect DBTAdapter::btMode, i.e. BTMode::BREDR, BTMode::LE or BTMode::DUAL to setup BREDR, LE or DUAL scanning! + // ERR_PRINT("Test"); + // throw jau::RuntimeException("Test", E_FILE_LINE); + + if( !isPowered() ) { + WARN_PRINT("DBTAdapter::startDiscovery: Adapter not powered: %s", toString().c_str()); + return HCIStatusCode::UNSPECIFIED_ERROR; + } + const std::lock_guard<std::mutex> lock(mtx_discovery); // RAII-style acquire and relinquish via destructor + + const ScanType currentNativeScanType = hci.getCurrentScanType(); + + if( hasScanType(currentNativeScanType, ScanType::LE) ) { + removeDiscoveredDevices(); + if( keep_le_scan_alive == keepAlive ) { + DBG_PRINT("DBTAdapter::startDiscovery: Already discovering, unchanged keepAlive %d -> %d, currentScanType[native %s, meta %s] ...", + keep_le_scan_alive.load(), keepAlive, + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(currentMetaScanType).c_str()); + } else { + DBG_PRINT("DBTAdapter::startDiscovery: Already discovering, changed keepAlive %d -> %d, currentScanType[native %s, meta %s] ...", + keep_le_scan_alive.load(), keepAlive, + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(currentMetaScanType).c_str()); + keep_le_scan_alive = keepAlive; + } + checkDiscoveryState(); + return HCIStatusCode::SUCCESS; + } + + DBG_PRINT("DBTAdapter::startDiscovery: Start: keepAlive %d -> %d, currentScanType[native %s, meta %s] ...", + keep_le_scan_alive.load(), keepAlive, + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(currentMetaScanType).c_str()); + + removeDiscoveredDevices(); + keep_le_scan_alive = keepAlive; + + // if le_enable_scan(..) is successful, it will issue 'mgmtEvDeviceDiscoveringHCI(..)' immediately, which updates currentMetaScanType. + const HCIStatusCode status = hci.le_start_scan(true /* filter_dup */, own_mac_type, le_scan_interval, le_scan_window); + + DBG_PRINT("DBTAdapter::startDiscovery: End: Result %s, keepAlive %d -> %d, currentScanType[native %s, meta %s] ...", + getHCIStatusCodeString(status).c_str(), keep_le_scan_alive.load(), keepAlive, + getScanTypeString(hci.getCurrentScanType()).c_str(), getScanTypeString(currentMetaScanType).c_str()); + + checkDiscoveryState(); + + return status; +} + +void BTAdapter::startDiscoveryBackground() noexcept { + // FIXME: Respect DBTAdapter::btMode, i.e. BTMode::BREDR, BTMode::LE or BTMode::DUAL to setup BREDR, LE or DUAL scanning! + if( !isPowered() ) { + WARN_PRINT("DBTAdapter::startDiscoveryBackground: Adapter not powered: %s", toString().c_str()); + return; + } + const std::lock_guard<std::mutex> lock(mtx_discovery); // RAII-style acquire and relinquish via destructor + if( !hasScanType(hci.getCurrentScanType(), ScanType::LE) && keep_le_scan_alive ) { // still? + // if le_enable_scan(..) is successful, it will issue 'mgmtEvDeviceDiscoveringHCI(..)' immediately, which updates currentMetaScanType. + const HCIStatusCode status = hci.le_enable_scan(true /* enable */); + if( HCIStatusCode::SUCCESS != status ) { + ERR_PRINT("DBTAdapter::startDiscoveryBackground: le_enable_scan failed: %s", getHCIStatusCodeString(status).c_str()); + } + checkDiscoveryState(); + } +} + +HCIStatusCode BTAdapter::stopDiscovery() noexcept { + // We allow !isEnabled, to utilize method for adjusting discovery state and notifying listeners + // FIXME: Respect DBTAdapter::btMode, i.e. BTMode::BREDR, BTMode::LE or BTMode::DUAL to stop BREDR, LE or DUAL scanning! + const std::lock_guard<std::mutex> lock(mtx_discovery); // RAII-style acquire and relinquish via destructor + /** + * Need to send mgmtEvDeviceDiscoveringMgmt(..) + * as manager/hci won't produce such event having temporarily disabled discovery. + * + --+-------+--------+-----------+----------------------------------------------------+ + * | # | meta | native | keepAlive | Note + * +---+-------+--------+-----------+----------------------------------------------------+ + * | 1 | true | true | false | - + * | 2 | false | false | false | - + * +---+-------+--------+-----------+----------------------------------------------------+ + * | 3 | true | true | true | - + * | 4 | true | false | true | temporarily disabled -> startDiscoveryBackground() + * | 5 | false | false | true | [4] -> [5] requires manual DISCOVERING event + * +---+-------+--------+-----------+----------------------------------------------------+ + * [4] current -> [5] post stopDiscovery == sendEvent + */ + const ScanType currentNativeScanType = hci.getCurrentScanType(); + const bool le_scan_temp_disabled = hasScanType(currentMetaScanType, ScanType::LE) && // true + !hasScanType(currentNativeScanType, ScanType::LE) && // false + keep_le_scan_alive; // true + + DBG_PRINT("DBTAdapter::stopDiscovery: Start: keepAlive %d, currentScanType[native %s, meta %s], le_scan_temp_disabled %d ...", + keep_le_scan_alive.load(), + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(currentMetaScanType).c_str(), + le_scan_temp_disabled); + + keep_le_scan_alive = false; + if( !hasScanType(currentMetaScanType, ScanType::LE) ) { + DBG_PRINT("DBTAdapter::stopDiscovery: Already disabled, keepAlive %d, currentScanType[native %s, meta %s] ...", + keep_le_scan_alive.load(), + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(currentMetaScanType).c_str()); + checkDiscoveryState(); + return HCIStatusCode::SUCCESS; + } + + HCIStatusCode status; + if( !adapterInfo.isCurrentSettingBitSet(AdapterSetting::POWERED) ) { + WARN_PRINT("DBTAdapter::stopDiscovery: Powered off: %s", toString().c_str()); + hci.setCurrentScanType(ScanType::NONE); + currentMetaScanType = ScanType::NONE; + status = HCIStatusCode::UNSPECIFIED_ERROR; + goto exit; + } + if( !hci.isOpen() ) { + ERR_PRINT("DBTAdapter::stopDiscovery: HCI closed: %s", toString().c_str()); + status = HCIStatusCode::UNSPECIFIED_ERROR; + goto exit; + } + + if( le_scan_temp_disabled ) { + // meta state transition [4] -> [5], w/o native disabling + // Will issue 'mgmtEvDeviceDiscoveringHCI(..)' immediately, which updates currentMetaScanType + // currentMetaScanType = changeScanType(currentMetaScanType, false, ScanType::LE); + status = HCIStatusCode::SUCCESS; // send event: discoveryTempDisabled + } else { + // if le_enable_scan(..) is successful, it will issue 'mgmtEvDeviceDiscoveringHCI(..)' immediately, which updates currentMetaScanType. + status = hci.le_enable_scan(false /* enable */); + if( HCIStatusCode::SUCCESS != status ) { + ERR_PRINT("DBTAdapter::stopDiscovery: le_enable_scan failed: %s", getHCIStatusCodeString(status).c_str()); + } + } + +exit: + if( le_scan_temp_disabled || HCIStatusCode::SUCCESS != status ) { + // In case of discoveryTempDisabled, power-off, le_enable_scane failure + // or already closed HCIHandler, send the event directly. + const MgmtEvtDiscovering e(dev_id, ScanType::LE, false); + mgmtEvDeviceDiscoveringHCI( e ); + } + DBG_PRINT("DBTAdapter::stopDiscovery: End: Result %s, keepAlive %d, currentScanType[native %s, meta %s], le_scan_temp_disabled %d ...", + getHCIStatusCodeString(status).c_str(), keep_le_scan_alive.load(), + getScanTypeString(hci.getCurrentScanType()).c_str(), getScanTypeString(currentMetaScanType).c_str(), le_scan_temp_disabled); + + checkDiscoveryState(); + + return status; +} + +// ************************************************* + +std::shared_ptr<BTDevice> BTAdapter::findDiscoveredDevice (const EUI48 & address, const BDAddressType addressType) noexcept { + const std::lock_guard<std::mutex> lock(mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + return findDevice(discoveredDevices, address, addressType); +} + +bool BTAdapter::addDiscoveredDevice(std::shared_ptr<BTDevice> const &device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + if( nullptr != findDevice(discoveredDevices, *device) ) { + // already discovered + return false; + } + discoveredDevices.push_back(device); + return true; +} + +bool BTAdapter::removeDiscoveredDevice(const BDAddressAndType & addressAndType) noexcept { + const std::lock_guard<std::mutex> lock(mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + for (auto it = discoveredDevices.begin(); it != discoveredDevices.end(); ) { + if ( nullptr != *it && addressAndType == (*it)->addressAndType ) { + it = discoveredDevices.erase(it); + return true; + } else { + ++it; + } + } + return false; +} + + +int BTAdapter::removeDiscoveredDevices() noexcept { + const std::lock_guard<std::mutex> lock(mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + int res = discoveredDevices.size(); + discoveredDevices.clear(); + return res; +} + +jau::darray<std::shared_ptr<BTDevice>> BTAdapter::getDiscoveredDevices() const noexcept { + const std::lock_guard<std::mutex> lock(const_cast<BTAdapter*>(this)->mtx_discoveredDevices); // RAII-style acquire and relinquish via destructor + device_list_t res = discoveredDevices; + return res; +} + +bool BTAdapter::addSharedDevice(std::shared_ptr<BTDevice> const &device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_sharedDevices); // RAII-style acquire and relinquish via destructor + if( nullptr != findDevice(sharedDevices, *device) ) { + // already shared + return false; + } + sharedDevices.push_back(device); + return true; +} + +std::shared_ptr<BTDevice> BTAdapter::getSharedDevice(const BTDevice & device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_sharedDevices); // RAII-style acquire and relinquish via destructor + return findDevice(sharedDevices, device); +} + +void BTAdapter::removeSharedDevice(const BTDevice & device) noexcept { + const std::lock_guard<std::mutex> lock(mtx_sharedDevices); // RAII-style acquire and relinquish via destructor + for (auto it = sharedDevices.begin(); it != sharedDevices.end(); ) { + if ( nullptr != *it && device == **it ) { + it = sharedDevices.erase(it); + return; // unique set + } else { + ++it; + } + } +} + +std::shared_ptr<BTDevice> BTAdapter::findSharedDevice (const EUI48 & address, const BDAddressType addressType) noexcept { + const std::lock_guard<std::mutex> lock(mtx_sharedDevices); // RAII-style acquire and relinquish via destructor + return findDevice(sharedDevices, address, addressType); +} + +void BTAdapter::removeDevice(BTDevice & device) noexcept { + WORDY_PRINT("DBTAdapter::removeDevice: Start %s", toString(false).c_str()); + const HCIStatusCode status = device.disconnect(HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION); + WORDY_PRINT("DBTAdapter::removeDevice: disconnect %s, %s", getHCIStatusCodeString(status).c_str(), toString(false).c_str()); + unlockConnect(device); + removeConnectedDevice(device); // usually done in DBTAdapter::mgmtEvDeviceDisconnectedHCI + removeDiscoveredDevice(device.addressAndType); // usually done in DBTAdapter::mgmtEvDeviceDisconnectedHCI + WORDY_PRINT("DBTAdapter::removeDevice: End %s", toString(false).c_str()); + removeSharedDevice(device); +} + +std::string BTAdapter::toString(bool includeDiscoveredDevices) const noexcept { + std::string out("Adapter[BTMode "+getBTModeString(btMode)+", "+getAddressString()+", '"+getName()+"', id "+std::to_string(dev_id)+ + ", curSettings"+getAdapterSettingMaskString(adapterInfo.getCurrentSettingMask())+ + ", scanType[native "+getScanTypeString(hci.getCurrentScanType())+", meta "+getScanTypeString(currentMetaScanType)+"]" + ", valid "+std::to_string(isValid())+", open[mgmt, "+std::to_string(mgmt.isOpen())+", hci "+std::to_string(hci.isOpen())+ + "], "+javaObjectToString()+"]"); + device_list_t devices = getDiscoveredDevices(); + if( includeDiscoveredDevices && devices.size() > 0 ) { + out.append("\n"); + for(auto it = devices.begin(); it != devices.end(); it++) { + std::shared_ptr<BTDevice> p = *it; + if( nullptr != p ) { + out.append(" ").append(p->toString()).append("\n"); + } + } + } + return out; +} + +// ************************************************* + +void BTAdapter::sendAdapterSettingsChanged(const AdapterSetting old_settings_, const AdapterSetting current_settings, AdapterSetting changes, + const uint64_t timestampMS) noexcept +{ + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + l->adapterSettingsChanged(*this, old_settings_, current_settings, changes, timestampMS); + } catch (std::exception &e) { + ERR_PRINT("DBTAdapter:CB:NewSettings-CBs %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), toString(false).c_str(), e.what()); + } + i++; + }); +} + +void BTAdapter::sendAdapterSettingsInitial(AdapterStatusListener & asl, const uint64_t timestampMS) noexcept +{ + const AdapterSetting current_settings = adapterInfo.getCurrentSettingMask(); + COND_PRINT(debug_event, "DBTAdapter::sendAdapterSettingsInitial: NONE -> %s, changes NONE: %s", + getAdapterSettingMaskString(current_settings).c_str(), toString(false).c_str() ); + try { + asl.adapterSettingsChanged(*this, AdapterSetting::NONE, current_settings, AdapterSetting::NONE, timestampMS); + } catch (std::exception &e) { + ERR_PRINT("DBTAdapter::sendAdapterSettingsChanged-CB: %s of %s: Caught exception %s", + asl.toString().c_str(), toString(false).c_str(), e.what()); + } +} + +void BTAdapter::sendDeviceUpdated(std::string cause, std::shared_ptr<BTDevice> device, uint64_t timestamp, EIRDataType updateMask) noexcept { + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*device) ) { + l->deviceUpdated(device, updateMask, timestamp); + } + } catch (std::exception &e) { + ERR_PRINT("DBTAdapter::sendDeviceUpdated-CBs (%s) %d/%zd: %s of %s: Caught exception %s", + cause.c_str(), i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), e.what()); + } + i++; + }); +} + +// ************************************************* + +bool BTAdapter::mgmtEvDeviceDiscoveringHCI(const MgmtEvent& e) noexcept { + return mgmtEvDeviceDiscoveringAny(e, true /* hciSourced */ ); +} + +bool BTAdapter::mgmtEvDeviceDiscoveringMgmt(const MgmtEvent& e) noexcept { + return mgmtEvDeviceDiscoveringAny(e, false /* hciSourced */ ); +} + +bool BTAdapter::mgmtEvDeviceDiscoveringAny(const MgmtEvent& e, const bool hciSourced) noexcept { + const std::string srctkn = hciSourced ? "hci" : "mgmt"; + const MgmtEvtDiscovering &event = *static_cast<const MgmtEvtDiscovering *>(&e); + const ScanType eventScanType = event.getScanType(); + const bool eventEnabled = event.getEnabled(); + ScanType currentNativeScanType = hci.getCurrentScanType(); + + // FIXME: Respect DBTAdapter::btMode, i.e. BTMode::BREDR, BTMode::LE or BTMode::DUAL to setup BREDR, LE or DUAL scanning! + // + // Also catches case where discovery changes w/o user interaction [start/stop]Discovery(..) + // if sourced from mgmt channel (!hciSourced) + + ScanType nextMetaScanType; + if( eventEnabled ) { + // enabled eventScanType + nextMetaScanType = changeScanType(currentMetaScanType, eventScanType, true); + } else { + // disabled eventScanType + if( hasScanType(eventScanType, ScanType::LE) && keep_le_scan_alive ) { + // Unchanged meta for disabled-LE && keep_le_scan_alive + nextMetaScanType = currentMetaScanType; + } else { + nextMetaScanType = changeScanType(currentMetaScanType, eventScanType, false); + } + } + + if( !hciSourced ) { + // update HCIHandler's currentNativeScanType from other source + const ScanType nextNativeScanType = changeScanType(currentNativeScanType, eventScanType, eventEnabled); + DBG_PRINT("DBTAdapter:%s:DeviceDiscovering: dev_id %d, keepDiscoveringAlive %d: scanType[native %s -> %s, meta %s -> %s]): %s", + srctkn.c_str(), dev_id, keep_le_scan_alive.load(), + getScanTypeString(currentNativeScanType).c_str(), getScanTypeString(nextNativeScanType).c_str(), + getScanTypeString(currentMetaScanType).c_str(), getScanTypeString(nextMetaScanType).c_str(), + event.toString().c_str()); + currentNativeScanType = nextNativeScanType; + hci.setCurrentScanType(currentNativeScanType); + } else { + DBG_PRINT("DBTAdapter:%s:DeviceDiscovering: dev_id %d, keepDiscoveringAlive %d: scanType[native %s, meta %s -> %s]): %s", + srctkn.c_str(), dev_id, keep_le_scan_alive.load(), + getScanTypeString(currentNativeScanType).c_str(), + getScanTypeString(currentMetaScanType).c_str(), getScanTypeString(nextMetaScanType).c_str(), + event.toString().c_str()); + } + currentMetaScanType = nextMetaScanType; + + checkDiscoveryState(); + + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + l->discoveringChanged(*this, currentMetaScanType, eventScanType, eventEnabled, keep_le_scan_alive, event.getTimestamp()); + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter:%s:DeviceDiscovering-CBs %d/%zd: %s of %s: Caught exception %s", + srctkn.c_str(), i+1, statusListenerList.size(), + l->toString().c_str(), toString().c_str(), except.what()); + } + i++; + }); + + if( !hasScanType(currentNativeScanType, ScanType::LE) && keep_le_scan_alive ) { + std::thread bg(&BTAdapter::startDiscoveryBackground, this); // @suppress("Invalid arguments") + bg.detach(); + } + return true; +} + +bool BTAdapter::mgmtEvNewSettingsMgmt(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter:mgmt:NewSettings: %s", e.toString().c_str()); + const MgmtEvtNewSettings &event = *static_cast<const MgmtEvtNewSettings *>(&e); + const AdapterSetting new_settings = adapterInfo.setCurrentSettingMask(event.getSettings()); // probably done by mgmt callback already + { + const BTMode _btMode = getAdapterSettingsBTMode(new_settings); + if( BTMode::NONE != _btMode ) { + btMode = _btMode; + } + } + const AdapterSetting old_settings_ = old_settings; + + const AdapterSetting changes = getAdapterSettingMaskDiff(new_settings, old_settings_); + + const bool justPoweredOn = isAdapterSettingBitSet(changes, AdapterSetting::POWERED) && + isAdapterSettingBitSet(new_settings, AdapterSetting::POWERED); + + const bool justPoweredOff = isAdapterSettingBitSet(changes, AdapterSetting::POWERED) && + !isAdapterSettingBitSet(new_settings, AdapterSetting::POWERED); + + old_settings = new_settings; + + COND_PRINT(debug_event, "DBTAdapter::mgmt:NewSettings: %s -> %s, changes %s: %s", + getAdapterSettingMaskString(old_settings_).c_str(), + getAdapterSettingMaskString(new_settings).c_str(), + getAdapterSettingMaskString(changes).c_str(), toString(false).c_str() ); + + if( justPoweredOn ) { + // Adapter has been powered on, ensure all hci states are reset. + hci.clearAllStates(); + } + sendAdapterSettingsChanged(old_settings_, new_settings, changes, event.getTimestamp()); + + if( justPoweredOff ) { + // Adapter has been powered off, close connections and cleanup off-thread. + std::thread bg(&BTAdapter::poweredOff, this); // @suppress("Invalid arguments") + bg.detach(); + } + + return true; +} + +bool BTAdapter::mgmtEvLocalNameChangedMgmt(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter:mgmt:LocalNameChanged: %s", e.toString().c_str()); + const MgmtEvtLocalNameChanged &event = *static_cast<const MgmtEvtLocalNameChanged *>(&e); + std::string old_name = localName.getName(); + std::string old_shortName = localName.getShortName(); + bool nameChanged = old_name != event.getName(); + bool shortNameChanged = old_shortName != event.getShortName(); + if( nameChanged ) { + localName.setName(event.getName()); + } + if( shortNameChanged ) { + localName.setShortName(event.getShortName()); + } + COND_PRINT(debug_event, "DBTAdapter:mgmt:LocalNameChanged: Local name: %d: '%s' -> '%s'; short_name: %d: '%s' -> '%s'", + nameChanged, old_name.c_str(), localName.getName().c_str(), + shortNameChanged, old_shortName.c_str(), localName.getShortName().c_str()); + (void)nameChanged; + (void)shortNameChanged; + return true; +} + +bool BTAdapter::mgmtEvDeviceConnectedHCI(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter:hci:DeviceConnected(dev_id %d): %s", dev_id, e.toString().c_str()); + const MgmtEvtDeviceConnected &event = *static_cast<const MgmtEvtDeviceConnected *>(&e); + EInfoReport ad_report; + { + ad_report.setSource(EInfoReport::Source::EIR); + ad_report.setTimestamp(event.getTimestamp()); + ad_report.setAddressType(event.getAddressType()); + ad_report.setAddress( event.getAddress() ); + ad_report.read_data(event.getData(), event.getDataSize()); + } + int new_connect = 0; + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr == device ) { + device = findDiscoveredDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + addSharedDevice(device); // connected devices must be in shared + discovered list + new_connect = 1; + } + } + if( nullptr == device ) { + device = findSharedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + addDiscoveredDevice(device); // connected devices must be in shared + discovered list + new_connect = 2; + } + } + if( nullptr == device ) { + // a whitelist auto-connect w/o previous discovery + device = BTDevice::make_shared(*this, ad_report); + addDiscoveredDevice(device); + addSharedDevice(device); + new_connect = 3; + } + + const SMPIOCapability io_cap_conn = mgmt.getIOCapability(dev_id); + + EIRDataType updateMask = device->update(ad_report); + if( 0 == new_connect ) { + WARN_PRINT("DBTAdapter::EventHCI:DeviceConnected(dev_id %d, already connected, updated %s): %s, handle %s -> %s,\n %s,\n -> %s", + dev_id, getEIRDataMaskString(updateMask).c_str(), event.toString().c_str(), + jau::uint16HexString(device->getConnectionHandle()).c_str(), jau::uint16HexString(event.getHCIHandle()).c_str(), + ad_report.toString().c_str(), + device->toString().c_str()); + } else { + addConnectedDevice(device); // track device, if not done yet + if( 2 <= new_connect ) { + device->ts_last_discovery = ad_report.getTimestamp(); + } + COND_PRINT(debug_event, "DBTAdapter::EventHCI:DeviceConnected(dev_id %d, new_connect %d, updated %s): %s, handle %s -> %s,\n %s,\n -> %s", + dev_id, new_connect, getEIRDataMaskString(updateMask).c_str(), event.toString().c_str(), + jau::uint16HexString(device->getConnectionHandle()).c_str(), jau::uint16HexString(event.getHCIHandle()).c_str(), + ad_report.toString().c_str(), + device->toString().c_str()); + } + device->notifyConnected(device, event.getHCIHandle(), io_cap_conn); + + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*device) ) { + if( EIRDataType::NONE != updateMask ) { + l->deviceUpdated(device, updateMask, ad_report.getTimestamp()); + } + if( 0 < new_connect ) { + l->deviceConnected(device, event.getHCIHandle(), event.getTimestamp()); + } + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter::EventHCI:DeviceConnected-CBs %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), except.what()); + } + i++; + }); + return true; +} + +bool BTAdapter::mgmtEvConnectFailedHCI(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter::EventHCI:ConnectFailed: %s", e.toString().c_str()); + const MgmtEvtDeviceConnectFailed &event = *static_cast<const MgmtEvtDeviceConnectFailed *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + const uint16_t handle = device->getConnectionHandle(); + COND_PRINT(debug_event, "DBTAdapter::EventHCI:ConnectFailed(dev_id %d): %s, handle %s -> zero,\n -> %s", + dev_id, event.toString().c_str(), jau::uint16HexString(handle).c_str(), + device->toString().c_str()); + + unlockConnect(*device); + device->notifyDisconnected(); + removeConnectedDevice(*device); + + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*device) ) { + l->deviceDisconnected(device, event.getHCIStatus(), handle, event.getTimestamp()); + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter::EventHCI:DeviceDisconnected-CBs %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), except.what()); + } + i++; + }); + removeDiscoveredDevice(device->addressAndType); // ensure device will cause a deviceFound event after disconnect + } else { + WORDY_PRINT("DBTAdapter::EventHCI:DeviceDisconnected(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvHCIEncryptionChangedHCI(const MgmtEvent& e) noexcept { + const MgmtEvtHCIEncryptionChanged &event = *static_cast<const MgmtEvtHCIEncryptionChanged *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + // BT Core Spec v5.2: Vol 4, Part E HCI: 7.7.8 HCIEventType::ENCRYPT_CHANGE + const HCIStatusCode evtStatus = event.getHCIStatus(); + const bool ok = HCIStatusCode::SUCCESS == evtStatus && 0 != event.getEncEnabled(); + const SMPPairingState pstate = ok ? SMPPairingState::COMPLETED : SMPPairingState::FAILED; + device->updatePairingState(device, e, evtStatus, pstate); + } else { + WORDY_PRINT("DBTAdapter::EventHCI:EncryptionChanged(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} +bool BTAdapter::mgmtEvHCIEncryptionKeyRefreshCompleteHCI(const MgmtEvent& e) noexcept { + const MgmtEvtHCIEncryptionKeyRefreshComplete &event = *static_cast<const MgmtEvtHCIEncryptionKeyRefreshComplete *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + // BT Core Spec v5.2: Vol 4, Part E HCI: 7.7.39 HCIEventType::ENCRYPT_KEY_REFRESH_COMPLETE + const HCIStatusCode evtStatus = event.getHCIStatus(); + // const bool ok = HCIStatusCode::SUCCESS == evtStatus; + // const SMPPairingState pstate = ok ? SMPPairingState::COMPLETED : SMPPairingState::FAILED; + const SMPPairingState pstate = SMPPairingState::NONE; + device->updatePairingState(device, e, evtStatus, pstate); + } else { + WORDY_PRINT("DBTAdapter::EventHCI:EncryptionKeyRefreshComplete(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvHCILERemoteUserFeaturesHCI(const MgmtEvent& e) noexcept { + const MgmtEvtHCILERemoteUserFeatures &event = *static_cast<const MgmtEvtHCILERemoteUserFeatures *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + COND_PRINT(debug_event, "DBTAdapter::EventHCI:LERemoteUserFeatures(dev_id %d): %s, %s", + dev_id, event.toString().c_str(), device->toString().c_str()); + + device->notifyLEFeatures(device, event.getFeatures()); + + } else { + WORDY_PRINT("DBTAdapter::EventHCI:LERemoteUserFeatures(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvDeviceDisconnectedHCI(const MgmtEvent& e) noexcept { + const MgmtEvtDeviceDisconnected &event = *static_cast<const MgmtEvtDeviceDisconnected *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + if( device->getConnectionHandle() != event.getHCIHandle() ) { + WORDY_PRINT("DBTAdapter::EventHCI:DeviceDisconnected(dev_id %d): ConnHandle mismatch %s\n -> %s", + dev_id, event.toString().c_str(), device->toString().c_str()); + return true; + } + COND_PRINT(debug_event, "DBTAdapter::EventHCI:DeviceDisconnected(dev_id %d): %s, handle %s -> zero,\n -> %s", + dev_id, event.toString().c_str(), jau::uint16HexString(event.getHCIHandle()).c_str(), + device->toString().c_str()); + + unlockConnect(*device); + device->notifyDisconnected(); + removeConnectedDevice(*device); + + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*device) ) { + l->deviceDisconnected(device, event.getHCIReason(), event.getHCIHandle(), event.getTimestamp()); + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter::EventHCI:DeviceDisconnected-CBs %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), except.what()); + } + i++; + }); + removeDiscoveredDevice(device->addressAndType); // ensure device will cause a deviceFound event after disconnect + } else { + WORDY_PRINT("DBTAdapter::EventHCI:DeviceDisconnected(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvDeviceDisconnectedMgmt(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter:mgmt:DeviceDisconnected: %s", e.toString().c_str()); + const MgmtEvtDeviceDisconnected &event = *static_cast<const MgmtEvtDeviceDisconnected *>(&e); + (void)event; + return true; +} + +bool BTAdapter::mgmtEvPairDeviceCompleteMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtPairDeviceComplete &event = *static_cast<const MgmtEvtPairDeviceComplete *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + const HCIStatusCode evtStatus = getHCIStatusCode( event.getStatus() ); + const bool ok = HCIStatusCode::ALREADY_PAIRED == evtStatus; + const SMPPairingState pstate = ok ? SMPPairingState::COMPLETED : SMPPairingState::NONE; + device->updatePairingState(device, e, evtStatus, pstate); + } else { + WORDY_PRINT("DBTAdapter::mgmt:PairDeviceComplete(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvNewLongTermKeyMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtNewLongTermKey& event = *static_cast<const MgmtEvtNewLongTermKey *>(&e); + const MgmtLongTermKeyInfo& ltk_info = event.getLongTermKey(); + std::shared_ptr<BTDevice> device = findConnectedDevice(ltk_info.address, ltk_info.address_type); + if( nullptr != device ) { + const bool ok = ltk_info.enc_size > 0 && ltk_info.key_type != MgmtLTKType::NONE; + if( ok ) { + device->updatePairingState(device, e, HCIStatusCode::SUCCESS, SMPPairingState::COMPLETED); + } else { + WORDY_PRINT("DBTAdapter::mgmt:NewLongTermKey(dev_id %d): Invalid LTK: %s", + dev_id, event.toString().c_str()); + } + } else { + WORDY_PRINT("DBTAdapter::mgmt:NewLongTermKey(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + +bool BTAdapter::mgmtEvDeviceFoundHCI(const MgmtEvent& e) noexcept { + COND_PRINT(debug_event, "DBTAdapter:hci:DeviceFound(dev_id %d): %s", dev_id, e.toString().c_str()); + const MgmtEvtDeviceFound &deviceFoundEvent = *static_cast<const MgmtEvtDeviceFound *>(&e); + + std::shared_ptr<EInfoReport> eir = deviceFoundEvent.getEIR(); + if( nullptr == eir ) { + // Sourced from Linux Mgmt or otherwise ... + eir = std::make_shared<EInfoReport>(); + eir->setSource(EInfoReport::Source::EIR_MGMT); + eir->setTimestamp(deviceFoundEvent.getTimestamp()); + eir->setEvtType(AD_PDU_Type::ADV_IND); + eir->setAddressType(deviceFoundEvent.getAddressType()); + eir->setAddress( deviceFoundEvent.getAddress() ); + eir->setRSSI( deviceFoundEvent.getRSSI() ); + eir->read_data(deviceFoundEvent.getData(), deviceFoundEvent.getDataSize()); + } // else: Sourced from HCIHandler via LE_ADVERTISING_REPORT (default!) + + std::shared_ptr<BTDevice> dev = findDiscoveredDevice(eir->getAddress(), eir->getAddressType()); + if( nullptr != dev ) { + // + // drop existing device + // + EIRDataType updateMask = dev->update(*eir); + COND_PRINT(debug_event, "DBTAdapter:hci:DeviceFound: Drop already discovered %s, %s", + dev->getAddressAndType().toString().c_str(), eir->toString().c_str()); + if( EIRDataType::NONE != updateMask ) { + sendDeviceUpdated("DiscoveredDeviceFound", dev, eir->getTimestamp(), updateMask); + } + return true; + } + + dev = findSharedDevice(eir->getAddress(), eir->getAddressType()); + if( nullptr != dev ) { + // + // active shared device, but flushed from discovered devices + // - update device + // - issue deviceFound, allowing receivers to recognize the re-discovered device + // - issue deviceUpdate if data has changed, allowing receivers to act upon + // + EIRDataType updateMask = dev->update(*eir); + addDiscoveredDevice(dev); // re-add to discovered devices! + dev->ts_last_discovery = eir->getTimestamp(); + COND_PRINT(debug_event, "DBTAdapter:hci:DeviceFound: Use already shared %s, %s", + dev->getAddressAndType().toString().c_str(), eir->toString().c_str()); + + int i=0; + bool device_used = false; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*dev) ) { + device_used = l->deviceFound(dev, eir->getTimestamp()) || device_used; + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter:hci:DeviceFound: %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), dev->toString().c_str(), except.what()); + } + i++; + }); + if( !device_used ) { + // keep to avoid duplicate finds: removeDiscoveredDevice(dev->addressAndType); + // and still allowing usage, as connecting will re-add to shared list + removeSharedDevice(*dev); // pending dtor if discovered is flushed + } else if( EIRDataType::NONE != updateMask ) { + sendDeviceUpdated("SharedDeviceFound", dev, eir->getTimestamp(), updateMask); + } + return true; + } + + // + // new device + // + dev = BTDevice::make_shared(*this, *eir); + addDiscoveredDevice(dev); + addSharedDevice(dev); + COND_PRINT(debug_event, "DBTAdapter:hci:DeviceFound: Use new %s, %s", + dev->getAddressAndType().toString().c_str(), eir->toString().c_str()); + + int i=0; + bool device_used = false; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*dev) ) { + device_used = l->deviceFound(dev, eir->getTimestamp()) || device_used; + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter:hci:DeviceFound-CBs %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), dev->toString().c_str(), except.what()); + } + i++; + }); + if( !device_used ) { + // keep to avoid duplicate finds: removeDiscoveredDevice(dev->addressAndType); + // and still allowing usage, as connecting will re-add to shared list + removeSharedDevice(*dev); // pending dtor if discovered is flushed + } + return true; +} + +bool BTAdapter::mgmtEvDeviceUnpairedMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtDeviceUnpaired &event = *static_cast<const MgmtEvtDeviceUnpaired *>(&e); + DBG_PRINT("DBTAdapter:mgmt:DeviceUnpaired: %s", event.toString().c_str()); + return true; +} +bool BTAdapter::mgmtEvPinCodeRequestMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtPinCodeRequest &event = *static_cast<const MgmtEvtPinCodeRequest *>(&e); + DBG_PRINT("DBTAdapter:mgmt:PinCodeRequest: %s", event.toString().c_str()); + return true; +} +bool BTAdapter::mgmtEvAuthFailedMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtAuthFailed &event = *static_cast<const MgmtEvtAuthFailed *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr == device ) { + WORDY_PRINT("DBTAdapter:hci:SMP: dev_id %d: Device not tracked: address[%s, %s], %s", + dev_id, event.getAddress().toString().c_str(), getBDAddressTypeString(event.getAddressType()).c_str(), + event.toString().c_str()); + return true; + } + const HCIStatusCode evtStatus = getHCIStatusCode( event.getStatus() ); + device->updatePairingState(device, e, evtStatus, SMPPairingState::FAILED); + return true; +} +bool BTAdapter::mgmtEvUserConfirmRequestMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtUserConfirmRequest &event = *static_cast<const MgmtEvtUserConfirmRequest *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr == device ) { + WORDY_PRINT("DBTAdapter:hci:SMP: dev_id %d: Device not tracked: address[%s, %s], %s", + dev_id, event.getAddress().toString().c_str(), getBDAddressTypeString(event.getAddressType()).c_str(), + event.toString().c_str()); + return true; + } + // FIXME: Pass confirm_hint and value? + device->updatePairingState(device, e, HCIStatusCode::SUCCESS, SMPPairingState::NUMERIC_COMPARE_EXPECTED); + return true; +} +bool BTAdapter::mgmtEvUserPasskeyRequestMgmt(const MgmtEvent& e) noexcept { + const MgmtEvtUserPasskeyRequest &event = *static_cast<const MgmtEvtUserPasskeyRequest *>(&e); + + std::shared_ptr<BTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr == device ) { + WORDY_PRINT("DBTAdapter:hci:SMP: dev_id %d: Device not tracked: address[%s, %s], %s", + dev_id, event.getAddress().toString().c_str(), getBDAddressTypeString(event.getAddressType()).c_str(), + event.toString().c_str()); + return true; + } + device->updatePairingState(device, e, HCIStatusCode::SUCCESS, SMPPairingState::PASSKEY_EXPECTED); + return true; +} + +bool BTAdapter::hciSMPMsgCallback(const BDAddressAndType & addressAndType, + const SMPPDUMsg& msg, const HCIACLData::l2cap_frame& source) noexcept { + std::shared_ptr<BTDevice> device = findConnectedDevice(addressAndType.address, addressAndType.type); + if( nullptr == device ) { + WORDY_PRINT("DBTAdapter:hci:SMP: dev_id %d: Device not tracked: address%s: %s, %s", + dev_id, addressAndType.toString().c_str(), + msg.toString().c_str(), source.toString().c_str()); + return true; + } + if( device->getConnectionHandle() != source.handle ) { + WORDY_PRINT("DBTAdapter:hci:SMP: dev_id %d: ConnHandle mismatch address%s: %s, %s\n -> %s", + dev_id, addressAndType.toString().c_str(), + msg.toString().c_str(), source.toString().c_str(), device->toString().c_str()); + return true; + } + + device->hciSMPMsgCallback(device, msg, source); + + return true; +} + +void BTAdapter::sendDevicePairingState(std::shared_ptr<BTDevice> device, const SMPPairingState state, const PairingMode mode, uint64_t timestamp) noexcept +{ + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + if( l->matchDevice(*device) ) { + l->devicePairingState(device, state, mode, timestamp); + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter::sendDevicePairingState: %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), except.what()); + } + i++; + }); +} + +void BTAdapter::sendDeviceReady(std::shared_ptr<BTDevice> device, uint64_t timestamp) noexcept { + int i=0; + jau::for_each_fidelity(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { + try { + // Only issue if valid && received connected confirmation (HCI) && not have called disconnect yet. + if( device->isValid() && device->getConnected() && device->allowDisconnect ) { + if( l->matchDevice(*device) ) { + l->deviceReady(device, timestamp); + } + } + } catch (std::exception &except) { + ERR_PRINT("DBTAdapter::sendDeviceReady: %d/%zd: %s of %s: Caught exception %s", + i+1, statusListenerList.size(), + l->toString().c_str(), device->toString().c_str(), except.what()); + } + i++; + }); +} |