diff options
author | Sven Gothel <[email protected]> | 2020-12-03 21:14:51 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-12-03 21:14:51 +0100 |
commit | 14cdab4ec36416b24e67c9bd8f3c9d979237f9f0 (patch) | |
tree | fbb9040b2d136388bf9de85606c7cf4998ae4c43 /src | |
parent | 23ce122d27eb4be942ec3918fee6a1cc1c3a7b0a (diff) |
DBTAdapter::[un]lockConnect[Any](..): New single device connect-command impl using mutex + condition-wait; Lock-free DBTDevice::setConn*Security*(..) ..
DBTAdapter::[un]lockConnect[Any](..): New single device connect-command impl using mutex + condition-wait;
- previous atomic use was too complicated
- only provide lockConnect(.., SMPIOCapability) and unlockConnect[Any](),
the former gets called at connect with cached SMPIOCapability user value
and the latter gets called ASAP after successful connection and L2CAP config
or at failure / power-off.
- to debug locking, set direct_bt.debug.adapter.lock environment variable,
e.g. setenv("direct_bt.debug", "true,adapter.lock", 1 /* overwrite */);
Lock-free DBTDevice::setConn*Security*(..) ..
- check constrains (already connected etc)
- cache SMPIOCapability for lockConnect() @ connect*() command
- cache BTSecurityLevel for L2CAP config when connected
Provide [DBT|Bluetooth]Device::setConnSecurityBest(..) for convenience.
Diffstat (limited to 'src')
-rw-r--r-- | src/direct_bt/DBTAdapter.cpp | 127 | ||||
-rw-r--r-- | src/direct_bt/DBTDevice.cpp | 64 |
2 files changed, 121 insertions, 70 deletions
diff --git a/src/direct_bt/DBTAdapter.cpp b/src/direct_bt/DBTAdapter.cpp index d3f19347..04d7e9fb 100644 --- a/src/direct_bt/DBTAdapter.cpp +++ b/src/direct_bt/DBTAdapter.cpp @@ -222,6 +222,7 @@ errout0: DBTAdapter::DBTAdapter() noexcept : debug_event(jau::environment::getBooleanProperty("direct_bt.debug.adapter.event", false)), + debug_lock(jau::environment::getBooleanProperty("direct_bt.debug.adapter.lock", false)), mgmt( DBTManager::get(BTMode::NONE /* use env default */) ), dev_id( mgmt.getDefaultAdapterDevID() ), hci( dev_id ) @@ -231,6 +232,7 @@ DBTAdapter::DBTAdapter() noexcept DBTAdapter::DBTAdapter(EUI48 &mac) noexcept : debug_event(jau::environment::getBooleanProperty("direct_bt.debug.adapter.event", false)), + debug_lock(jau::environment::getBooleanProperty("direct_bt.debug.adapter.lock", false)), mgmt( DBTManager::get(BTMode::NONE /* use env default */) ), dev_id( mgmt.findAdapterInfoDevId(mac) ), hci( dev_id ) @@ -240,6 +242,7 @@ DBTAdapter::DBTAdapter(EUI48 &mac) noexcept DBTAdapter::DBTAdapter(const int _dev_id) noexcept : debug_event(jau::environment::getBooleanProperty("direct_bt.debug.adapter.event", false)), + debug_lock(jau::environment::getBooleanProperty("direct_bt.debug.adapter.lock", false)), mgmt( DBTManager::get(BTMode::NONE /* use env default */) ), dev_id( 0 <= _dev_id ? _dev_id : mgmt.getDefaultAdapterDevID() ), hci( dev_id ) @@ -315,8 +318,7 @@ void DBTAdapter::poweredOff() noexcept { // ensure all hci states are reset. hci.clearAllStates(); - iocap_defaultval = SMPIOCapability::UNSET; - conn_blocking_device_ptr = nullptr; + unlockConnectAny(); DBG_PRINT("DBTAdapter::poweredOff: XXX"); } @@ -355,72 +357,115 @@ bool DBTAdapter::setPowered(bool value) noexcept { return mgmt.setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, value ? 1 : 0, current_settings); } -bool DBTAdapter::lockConnect(const DBTDevice & device, const bool wait) noexcept { - const DBTDevice * exp_device = nullptr; // C++11, exp as value since C++20 +bool DBTAdapter::lockConnect(const DBTDevice & 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? - const uint32_t poll_period_ms = 125; - uint32_t td = 0; - while( !conn_blocking_device_ptr.compare_exchange_strong(exp_device, &device) ) { - if( device == *exp_device ) { + + 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 ) { - if( timeout_ms <= td ) { - return false; // already set, timeout, blocked + 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; + } } - std::this_thread::sleep_for(std::chrono::milliseconds(poll_period_ms)); - td += poll_period_ms; - exp_device = nullptr; // continue wait for nullptr + // 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 } } - // newly locked: iocap_blocking_device_ptr := &device - return true; -} + single_conn_device_ptr = &device; -bool DBTAdapter::setConnIOCapability(const DBTDevice & device, const SMPIOCapability io_cap, bool& blocking, SMPIOCapability& pre_io_cap) noexcept { - if( SMPIOCapability::UNSET == io_cap ) { - blocking = false; - return false; - } - if( !lockConnect(device, blocking) ) { - blocking = true; - return false; + if( SMPIOCapability::UNSET != io_cap ) { + 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: 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, no io-cap: %s", device.toString(false).c_str()); + return true; } - blocking = false; - const bool res = mgmt.setIOCapability(dev_id, io_cap, pre_io_cap); - if( res ) { - iocap_defaultval = pre_io_cap; +} + +bool DBTAdapter::unlockConnect(const DBTDevice & 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 ) { + 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 { - conn_blocking_device_ptr = nullptr; + 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 DBTAdapter::unlockConnect(const DBTDevice & device) noexcept { - SMPIOCapability pre_io_cap { SMPIOCapability::UNSET }; - return unlockConnect(device, pre_io_cap); -} +bool DBTAdapter::unlockConnectAny() noexcept { + std::unique_lock<std::mutex> lock(mtx_single_conn_device); // RAII-style acquire and relinquish via destructor -bool DBTAdapter::unlockConnect(const DBTDevice & device, SMPIOCapability& pre_io_cap) noexcept { - if( nullptr != conn_blocking_device_ptr && device == *conn_blocking_device_ptr ) { + if( nullptr != single_conn_device_ptr ) { const SMPIOCapability v = iocap_defaultval; iocap_defaultval = SMPIOCapability::UNSET; if( SMPIOCapability::UNSET != v ) { SMPIOCapability o; const bool res = mgmt.setIOCapability(dev_id, v, o); - DBG_PRINT("DBTAdapter::resetConnIOCapability: result %d: %s -> %s, %s", + COND_PRINT(debug_lock, "DBTAdapter::unlockConnectAny: Success: setIOCapability[res %d: %s -> %s]; %s", res, getSMPIOCapabilityString(o).c_str(), getSMPIOCapabilityString(v).c_str(), - device.toString(false).c_str()); - if( res ) { - pre_io_cap = o; - } + 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()); } - conn_blocking_device_ptr = nullptr; + 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; } } diff --git a/src/direct_bt/DBTDevice.cpp b/src/direct_bt/DBTDevice.cpp index bfab960e..01aef32b 100644 --- a/src/direct_bt/DBTDevice.cpp +++ b/src/direct_bt/DBTDevice.cpp @@ -323,7 +323,7 @@ HCIStatusCode DBTDevice::connectLE(uint16_t le_scan_interval, uint16_t le_scan_w ERR_PRINT("DBTDevice::connectLE: HCI closed: %s", toString(false).c_str()); return HCIStatusCode::INTERNAL_FAILURE; } - if( !adapter.lockConnect(*this, true /* wait */) ) { + if( !adapter.lockConnect(*this, true /* wait */, pairing_data.ioCap_user) ) { ERR_PRINT("DBTDevice::connectLE: adapter::lockConnect() failed: %s", toString(false).c_str()); return HCIStatusCode::INTERNAL_FAILURE; } @@ -372,7 +372,7 @@ HCIStatusCode DBTDevice::connectBREDR(const uint16_t pkt_type, const uint16_t cl ERR_PRINT("DBTDevice::connectBREDR: HCI closed: %s", toString(false).c_str()); return HCIStatusCode::INTERNAL_FAILURE; } - if( !adapter.lockConnect(*this, true /* wait */) ) { + if( !adapter.lockConnect(*this, true /* wait */, pairing_data.ioCap_user) ) { ERR_PRINT("DBTDevice::connectBREDR: adapter::lockConnect() failed: %s", toString(false).c_str()); return HCIStatusCode::INTERNAL_FAILURE; } @@ -834,18 +834,16 @@ bool DBTDevice::setConnSecurityLevel(const BTSecurityLevel sec_level) noexcept { getBTSecurityLevelString(sec_level).c_str(), toString(false).c_str()); return false; } - const BTSecurityLevel old_sec_level = pairing_data.sec_level_user; - pairing_data.sec_level_user = sec_level; const bool res = true; + pairing_data.sec_level_user = sec_level; - DBG_PRINT("DBTDevice::setConnSecurityLevel: result %d: lvl %s -> %s, %s", res, - getBTSecurityLevelString(old_sec_level).c_str(), + DBG_PRINT("DBTDevice::setConnSecurityLevel: result %d: lvl %s, %s", res, getBTSecurityLevelString(sec_level).c_str(), toString(false).c_str()); return res; } -bool DBTDevice::setConnIOCapability(const SMPIOCapability io_cap, bool& blocking, SMPIOCapability& pre_io_cap) noexcept { +bool DBTDevice::setConnIOCapability(const SMPIOCapability io_cap) noexcept { if( SMPIOCapability::UNSET == io_cap ) { DBG_PRINT("DBTDevice::setConnIOCapability: io %s, invalid value.", getSMPIOCapabilityString(io_cap).c_str()); return false; @@ -856,19 +854,17 @@ bool DBTDevice::setConnIOCapability(const SMPIOCapability io_cap, bool& blocking getSMPIOCapabilityString(io_cap).c_str(), toString(false).c_str()); return false; } + const bool res = true; + pairing_data.ioCap_user = io_cap; - const bool blocking_call = blocking; - const bool res = adapter.setConnIOCapability(*this, io_cap, blocking, pre_io_cap); - - DBG_PRINT("DBTDevice::setConnIOCapability: result %d (blocked %d): io %s -> %s (blocking %d), %s", - res, blocking, - getSMPIOCapabilityString(pre_io_cap).c_str(), getSMPIOCapabilityString(io_cap).c_str(), - blocking_call, toString(false).c_str()); + DBG_PRINT("DBTDevice::setConnIOCapability: result %d: io %s, %s", res, + getSMPIOCapabilityString(io_cap).c_str(), + toString(false).c_str()); return res; } -bool DBTDevice::setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapability io_cap, bool& blocking, SMPIOCapability& pre_io_cap) noexcept { +bool DBTDevice::setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapability io_cap) noexcept { if( BTSecurityLevel::UNSET == sec_level ) { DBG_PRINT("DBTAdapter::setConnSecurity: lvl %s, invalid value.", getBTSecurityLevelString(sec_level).c_str()); return false; @@ -884,25 +880,34 @@ bool DBTDevice::setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapa getSMPIOCapabilityString(io_cap).c_str(), toString(false).c_str()); return false; } + const bool res = true; + pairing_data.ioCap_user = io_cap; + pairing_data.sec_level_user = sec_level; - const BTSecurityLevel pre_sec_level = pairing_data.sec_level_user; - - const bool blocking_call = blocking; - const bool res = adapter.setConnIOCapability(*this, io_cap, blocking, pre_io_cap); - if( res ) { - pairing_data.sec_level_user = sec_level; - } - - DBG_PRINT("DBTDevice::setConnSecurity: result %d (blocked %d): lvl %s -> %s, io %s -> %s (blocking %d), %s", - res, blocking, - getBTSecurityLevelString(pre_sec_level).c_str(), - getBTSecurityLevelString(pairing_data.sec_level_user).c_str(), - getSMPIOCapabilityString(pre_io_cap).c_str(), getSMPIOCapabilityString(io_cap).c_str(), - blocking_call, toString(false).c_str()); + DBG_PRINT("DBTDevice::setConnSecurity: result %d: lvl %s, io %s, %s", res, + getBTSecurityLevelString(sec_level).c_str(), + getSMPIOCapabilityString(io_cap).c_str(), + toString(false).c_str()); return res; } +bool DBTDevice::setConnSecurityBest(const BTSecurityLevel sec_level, const SMPIOCapability io_cap) noexcept { + if( BTSecurityLevel::UNSET < sec_level && SMPIOCapability::UNSET != io_cap ) { + return setConnSecurity(sec_level, io_cap); + } else if( BTSecurityLevel::UNSET < sec_level ) { + if( BTSecurityLevel::ENC_ONLY >= sec_level ) { + return setConnSecurity(sec_level, SMPIOCapability::NO_INPUT_NO_OUTPUT); + } else { + return setConnSecurityLevel(sec_level); + } + } else if( SMPIOCapability::UNSET != io_cap ) { + return setConnIOCapability(io_cap); + } else { + return false; + } +} + HCIStatusCode DBTDevice::setPairingPasskey(const uint32_t passkey) noexcept { const std::lock_guard<std::mutex> lock(mtx_pairing); // RAII-style acquire and relinquish via destructor @@ -956,6 +961,7 @@ void DBTDevice::clearSMPStates(const bool connected) noexcept { if( !connected ) { // needs to survive connected, or will be set right @ connected + pairing_data.ioCap_user = SMPIOCapability::UNSET; pairing_data.ioCap_conn = SMPIOCapability::UNSET; pairing_data.sec_level_user = BTSecurityLevel::UNSET; } |