diff options
author | Sven Gothel <[email protected]> | 2020-11-23 16:45:16 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-11-23 16:45:16 +0100 |
commit | 652e3fc523ac62abddc1afba8468ac601a153464 (patch) | |
tree | 577d68c4284a2431720acf7d9f1177e4d581acb7 | |
parent | bb51c620dac82f4b0fb7769a7cdeafc2dc6f77cd (diff) |
Enc/Auth: Allow full PairingMode modulation via BTSecurityLevel and SMPIOCapability: Like force JUST_WORKS by BTSecurityLevel::ENC_ONLY or SMPIOCapability::NO_INPUT_NO_OUTPUT
To allow non-auth encryption mode, user _must_ set BTSecurityLevel, SMPIOCapability or both appropriately.
Otherwise BlueZ/Kernel will chose authenticated SMP negotiation.
- DBTDevice::setConnSecurityLevel(): Will adjust SMPIOCapability automatically, if not yet set
- DBTDevice::setConnIOCapability() and DBTDevice::setConnSecurity(): Perform plain setting
- DBTDevice::processL2CAPSetup(): Sets BTSecurityLevel appropriately either if no-auth SMPIOCapability::NO_INPUT_NO_OUTPUT is chosen,
or based on LE_Enc feature bit and/or SC capability.
Using new HCI ENCRYPT_CHANGE and ENCRYPT_KEY_REFRESH_COMPLETE, for non-auth BTSecurityLevel::ENC_ONLY
endpoint to set SMPPairingState::PROCESS_COMPLETED + PairingMode::JUST_WORKS.
Note: In the non-auth legacy mode, the SMP (ACL.SMP) is not being used.
....
-rw-r--r-- | api/direct_bt/DBTAdapter.hpp | 29 | ||||
-rw-r--r-- | api/direct_bt/DBTDevice.hpp | 71 | ||||
-rw-r--r-- | examples/direct_bt_scanner10/dbt_scanner10.cpp | 15 | ||||
-rw-r--r-- | examples/java/DBTScanner10.java | 16 | ||||
-rw-r--r-- | java/direct_bt/tinyb/DBTDevice.java | 31 | ||||
-rw-r--r-- | java/jni/direct_bt/DBTDevice.cxx | 47 | ||||
-rw-r--r-- | java/org/tinyb/BluetoothDevice.java | 66 | ||||
-rw-r--r-- | java/tinyb/dbus/DBusDevice.java | 14 | ||||
-rw-r--r-- | src/direct_bt/DBTAdapter.cpp | 138 | ||||
-rw-r--r-- | src/direct_bt/DBTDevice.cpp | 141 |
10 files changed, 476 insertions, 92 deletions
diff --git a/api/direct_bt/DBTAdapter.hpp b/api/direct_bt/DBTAdapter.hpp index 85d1a618..f6f159a5 100644 --- a/api/direct_bt/DBTAdapter.hpp +++ b/api/direct_bt/DBTAdapter.hpp @@ -214,6 +214,9 @@ namespace direct_bt { std::atomic<ScanType> currentMetaScanType; // = ScanType::NONE std::atomic<bool> keep_le_scan_alive; // = false; + jau::ordered_atomic<SMPIOCapability, std::memory_order_relaxed> io_capability_defaultval = SMPIOCapability::UNSET; + std::atomic<const DBTDevice *> io_capability_device_ptr = nullptr; + std::vector<std::shared_ptr<DBTDevice>> connectedDevices; std::vector<std::shared_ptr<DBTDevice>> discoveredDevices; // all discovered devices std::vector<std::shared_ptr<DBTDevice>> sharedDevices; // All active shared devices. Final holder of DBTDevice lifecycle! @@ -246,10 +249,30 @@ namespace direct_bt { uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout); friend HCIStatusCode DBTDevice::connectBREDR(const uint16_t pkt_type, const uint16_t clock_offset, const uint8_t role_switch); + friend bool DBTDevice::setConnSecurityLevel(const BTSecurityLevel sec_level, const bool blocking) noexcept; + friend bool DBTDevice::setConnIOCapability(const SMPIOCapability io_cap, const bool blocking) noexcept; + friend bool DBTDevice::setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapability io_cap, const bool blocking) noexcept; + friend void DBTDevice::processL2CAPSetup(std::shared_ptr<DBTDevice> sthis); + friend bool DBTDevice::updatePairingState(std::shared_ptr<DBTDevice> sthis, SMPPairingState state, std::shared_ptr<MgmtEvent> evt) noexcept; friend void DBTDevice::hciSMPMsgCallback(std::shared_ptr<DBTDevice> sthis, std::shared_ptr<const SMPPDUMsg> msg, const HCIACLData::l2cap_frame& source) noexcept; friend void DBTDevice::processDeviceReady(std::shared_ptr<DBTDevice> sthis, const uint64_t timestamp); friend std::vector<std::shared_ptr<GATTService>> DBTDevice::getGATTServices() noexcept; + /** + * Sets the given SMPIOCapability for the next DBTDevice::connectLE() or DBTDevice::connectBREDR(). + * <p> + * The SMPIOCapability value will be reset to its previous value when connection is completed or failed. + * </p> + * @param io_cap SMPIOCapability to be applied + * @param blocking if true, blocks until previous setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. + */ + bool setConnIOCapability(const DBTDevice & device, const SMPIOCapability io_cap, const bool blocking) noexcept; + bool resetConnIOCapability(const DBTDevice & device) noexcept; + bool resetConnIOCapability(const DBTDevice & device, SMPIOCapability& pre_io_cap) noexcept; + bool isConnIOCapabilitySet() const noexcept { return nullptr != io_capability_device_ptr; } + bool addConnectedDevice(const std::shared_ptr<DBTDevice> & device) noexcept; bool removeConnectedDevice(const DBTDevice & device) noexcept; int disconnectAllDevices(const HCIStatusCode reason=HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION ) noexcept; @@ -274,9 +297,12 @@ namespace direct_bt { bool mgmtEvDeviceDiscoveringHCI(std::shared_ptr<MgmtEvent> e) noexcept; bool mgmtEvDeviceConnectedHCI(std::shared_ptr<MgmtEvent> e) noexcept; bool mgmtEvConnectFailedHCI(std::shared_ptr<MgmtEvent> e) noexcept; - bool mgmtEvLERemoteUserFeaturesHCI(std::shared_ptr<MgmtEvent> e) noexcept; + bool mgmtEvHCILERemoteUserFeaturesHCI(std::shared_ptr<MgmtEvent> e) noexcept; + bool mgmtEvHCIEncryptionChangedHCI(std::shared_ptr<MgmtEvent> e) noexcept; + bool mgmtEvHCIEncryptionKeyRefreshCompleteHCI(std::shared_ptr<MgmtEvent> e) noexcept; bool mgmtEvDeviceDisconnectedHCI(std::shared_ptr<MgmtEvent> e) noexcept; + bool mgmtEvDeviceDiscoveringAny(std::shared_ptr<MgmtEvent> e, const bool hciSourced) noexcept; bool mgmtEvPinCodeRequestMgmt(std::shared_ptr<MgmtEvent> e) noexcept; @@ -285,7 +311,6 @@ namespace direct_bt { bool mgmtEvAuthFailedMgmt(std::shared_ptr<MgmtEvent> e) noexcept; bool mgmtEvDeviceUnpairedMgmt(std::shared_ptr<MgmtEvent> e) noexcept; - void updatePairingState(std::shared_ptr<DBTDevice> device, const SMPPairingState pstate, uint64_t timestamp) noexcept; bool hciSMPMsgCallback(const EUI48& address, BDAddressType addressType, std::shared_ptr<const SMPPDUMsg> msg, const HCIACLData::l2cap_frame& source) noexcept; void sendDevicePairingState(std::shared_ptr<DBTDevice> device, const SMPPairingState state, const PairingMode mode, uint64_t timestamp) noexcept; diff --git a/api/direct_bt/DBTDevice.hpp b/api/direct_bt/DBTDevice.hpp index 6e0f322f..759add9d 100644 --- a/api/direct_bt/DBTDevice.hpp +++ b/api/direct_bt/DBTDevice.hpp @@ -39,6 +39,7 @@ #include "HCIIoctl.hpp" #include "HCIComm.hpp" +#include "MgmtTypes.hpp" #include "SMPHandler.hpp" #include "GATTHandler.hpp" @@ -80,10 +81,14 @@ namespace direct_bt { std::atomic<bool> allowDisconnect; // allowDisconnect = isConnected || 'isConnectIssued' struct PairingData { - jau::ordered_atomic<BTSecurityLevel, std::memory_order_relaxed> sec_level=BTSecurityLevel::UNSET, sec_level_user=BTSecurityLevel::UNSET; + jau::ordered_atomic<SMPIOCapability, std::memory_order_relaxed> ioCap_conn=SMPIOCapability::UNSET; + jau::ordered_atomic<BTSecurityLevel, std::memory_order_relaxed> sec_level_conn=BTSecurityLevel::UNSET; + jau::ordered_atomic<BTSecurityLevel, std::memory_order_relaxed> sec_level_user=BTSecurityLevel::UNSET; + jau::ordered_atomic<SMPPairingState, std::memory_order_relaxed> state; jau::ordered_atomic<PairingMode, std::memory_order_relaxed> mode; jau::relaxed_atomic_bool res_requested_sec; + SMPAuthReqs authReqs_init, authReqs_resp; SMPIOCapability ioCap_init, ioCap_resp; SMPOOBDataFlag oobFlag_init, oobFlag_resp; @@ -109,7 +114,7 @@ namespace direct_bt { EIRDataType update(GattGenericAccessSvc const &data, const uint64_t timestamp) noexcept; void notifyDisconnected() noexcept; - void notifyConnected(std::shared_ptr<DBTDevice> sthis, const uint16_t handle) noexcept; + void notifyConnected(std::shared_ptr<DBTDevice> sthis, const uint16_t handle, const SMPIOCapability io_cap) noexcept; void notifyLEFeatures(std::shared_ptr<DBTDevice> sthis, const LEFeatures features) noexcept; /** @@ -168,7 +173,7 @@ namespace direct_bt { */ bool connectGATT(std::shared_ptr<DBTDevice> sthis) noexcept; - bool updatePairingState_locked(const SMPPairingState state, PairingMode& current_mode) noexcept; + bool updatePairingState(std::shared_ptr<DBTDevice> sthis, SMPPairingState state, std::shared_ptr<MgmtEvent> evt) noexcept; /** * Will be performed within disconnect() and notifyDisconnected(). @@ -425,14 +430,66 @@ namespace direct_bt { HCIStatusCode disconnect(const HCIStatusCode reason=HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION ) noexcept; /** - * Set the overriding security level used at device connection. + * Set the BTSecurityLevel used to connect to this device. + * <p> + * Method returns false if this device has already being connected, + * or DBTDevice::connectLE() or DBTDevice::connectBREDR() has been issued already. + * </p> + * <p> + * To ensure consistent no authentication setup,<br> + * implementation will set SMPIOCapability::NO_INPUT_NO_OUTPUT if sec_level <= BTSecurityLevel::ENC_ONLY<br> + * and DBTDevice::setConnIOCapability() not used. + * </p> + * @param sec_level BTSecurityLevel to be applied + * @param blocking if true, blocks until previous SMPIOCapability setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. + * @return + */ + bool setConnSecurityLevel(const BTSecurityLevel sec_level, const bool blocking) noexcept; + + /** + * Return the BTSecurityLevel, determined when connection is established. + */ + BTSecurityLevel getConnSecurityLevel() const noexcept { return pairing_data.sec_level_conn; } + + /** + * Sets the given SMPIOCapability used to connect to this device. + * <p> + * Method returns false if operation fails, this device has already being connected, + * or DBTDevice::connectLE() or DBTDevice::connectBREDR() has been issued already. + * </p> + * <p> + * The SMPIOCapability value will be reset to its previous value when connection is completed or failed. + * </p> + * @param io_cap SMPIOCapability to be applied + * @param blocking if true, blocks until previous SMPIOCapability setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. + */ + bool setConnIOCapability(const SMPIOCapability io_cap, const bool blocking=true) noexcept; + + /** + * Sets the given BTSecurityLevel and SMPIOCapability used to connect to this device. + * <p> + * Method returns false if operation fails, this device has already being connected, + * or DBTDevice::connectLE() or DBTDevice::connectBREDR() has been issued already. + * </p> + * <p> + * The SMPIOCapability value will be reset to its previous value when connection is completed or failed. + * </p> + * @param sec_level BTSecurityLevel to be applied + * @param io_cap SMPIOCapability to be applied + * @param blocking if true, blocks until previous SMPIOCapability setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. */ - void setSecurityLevel(const BTSecurityLevel sec_level) noexcept { pairing_data.sec_level_user = sec_level; } + bool setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapability io_cap, const bool blocking=true) noexcept; /** - * Return the currently set security level. + * Return the set SMPIOCapability value, determined when connection is established. */ - BTSecurityLevel getCurrentSecurityLevel() const noexcept { return pairing_data.sec_level; } + SMPIOCapability getConnIOCapability() const noexcept { return pairing_data.ioCap_conn; } /** * Method sets the given passkey entry, see PairingMode::PASSKEY_ENTRY_ini. diff --git a/examples/direct_bt_scanner10/dbt_scanner10.cpp b/examples/direct_bt_scanner10/dbt_scanner10.cpp index f757d422..c2773cf4 100644 --- a/examples/direct_bt_scanner10/dbt_scanner10.cpp +++ b/examples/direct_bt_scanner10/dbt_scanner10.cpp @@ -79,6 +79,7 @@ static std::vector<EUI48> waitForDevices; const static uint32_t NO_PASSKEY = 0xffffffffU; static uint32_t pairing_passkey = NO_PASSKEY; static BTSecurityLevel sec_level = BTSecurityLevel::UNSET; +static SMPIOCapability io_capabilities = SMPIOCapability::UNSET; static void connectDiscoveredDevice(std::shared_ptr<DBTDevice> device); @@ -362,9 +363,15 @@ class MyGATTEventListener : public AssociatedGATTCharacteristicListener { static void connectDiscoveredDevice(std::shared_ptr<DBTDevice> device) { fprintf(stderr, "****** Connecting Device: Start %s\n", device->toString().c_str()); device->getAdapter().stopDiscovery(); - if( BTSecurityLevel::UNSET < sec_level ) { - device->setSecurityLevel(sec_level); + + if( BTSecurityLevel::UNSET < sec_level && SMPIOCapability::UNSET != io_capabilities ) { + device->setConnSecurity(sec_level, io_capabilities, true /* blocking */); + } else if( BTSecurityLevel::UNSET < sec_level ) { + device->setConnSecurityLevel(sec_level, true /* blocking */); + } else if( SMPIOCapability::UNSET != io_capabilities ) { + device->setConnIOCapability(io_capabilities, true /* blocking */); } + HCIStatusCode res; if( !USE_WHITELIST ) { res = device->connectDefault(); @@ -755,6 +762,8 @@ int main(int argc, char *argv[]) pairing_passkey = atoi(argv[++i]); } else if( !strcmp("-seclevel", argv[i]) && argc > (i+1) ) { sec_level = getBTSecurityLevel(atoi(argv[++i])); + } else if( !strcmp("-iocap", argv[i]) && argc > (i+1) ) { + io_capabilities = getSMPIOCapability(atoi(argv[++i])); } else if( !strcmp("-charid", argv[i]) && argc > (i+1) ) { charIdentifier = std::string(argv[++i]); } else if( !strcmp("-charval", argv[i]) && argc > (i+1) ) { @@ -779,7 +788,7 @@ int main(int argc, char *argv[]) "[-disconnect] [-enableGATTPing] [-count <number>] [-single] [-show_update_events] [-quiet] " "[-resetEachCon connectionCount] " "(-mac <device_address>)* (-wl <device_address>)* " - "[-passkey <digits>] [-seclevel <int>]" + "[-seclevel <int>] [-iocap <int>] [-passkey <digits>]" "[-charid <uuid>] [-charval <byte-val>]" "[-dbt_verbose true|false] " "[-dbt_debug true|false|adapter.event,gatt.data,hci.event,mgmt.event] " diff --git a/examples/java/DBTScanner10.java b/examples/java/DBTScanner10.java index e1696bcd..44d7f9d6 100644 --- a/examples/java/DBTScanner10.java +++ b/examples/java/DBTScanner10.java @@ -57,6 +57,7 @@ import org.tinyb.GATTCharacteristicListener; import org.tinyb.HCIStatusCode; import org.tinyb.HCIWhitelistConnectType; import org.tinyb.PairingMode; +import org.tinyb.SMPIOCapability; import org.tinyb.SMPPairingState; import org.tinyb.ScanType; import org.tinyb.BluetoothManager.ChangedAdapterSetListener; @@ -79,6 +80,7 @@ public class DBTScanner10 { static final int NO_PASSKEY = 0xffffffff; int pairing_passkey = NO_PASSKEY; BTSecurityLevel sec_level = BTSecurityLevel.UNSET; + SMPIOCapability io_capabilities = SMPIOCapability.UNSET; long timestamp_t0; @@ -289,9 +291,15 @@ public class DBTScanner10 { final HCIStatusCode r = device.getAdapter().stopDiscovery(); println("****** Connecting Device: stopDiscovery result "+r); } - if( BTSecurityLevel.UNSET.value < sec_level.value ) { - device.setSecurityLevel(sec_level); + + if( BTSecurityLevel.UNSET.value < sec_level.value && SMPIOCapability.UNSET.value != io_capabilities.value ) { + device.setConnSecurity(sec_level, io_capabilities, true /* blocking */); + } else if( BTSecurityLevel.UNSET.value < sec_level.value ) { + device.setConnSecurityLevel(sec_level, true /* blocking */); + } else if( SMPIOCapability.UNSET.value != io_capabilities.value ) { + device.setConnIOCapability(io_capabilities, true /* blocking */); } + HCIStatusCode res; if( !USE_WHITELIST ) { res = device.connect(); @@ -732,6 +740,8 @@ public class DBTScanner10 { test.pairing_passkey = Integer.valueOf(args[++i]).intValue(); } else if( arg.equals("-seclevel") && args.length > (i+1) ) { test.sec_level = BTSecurityLevel.get( (byte)Integer.valueOf(args[++i]).intValue() ); + } else if( arg.equals("-iocap") && args.length > (i+1) ) { + test.io_capabilities = SMPIOCapability.get( (byte)Integer.valueOf(args[++i]).intValue() ); } else if( arg.equals("-charid") && args.length > (i+1) ) { test.charIdentifier = args[++i]; } else if( arg.equals("-charval") && args.length > (i+1) ) { @@ -755,7 +765,7 @@ public class DBTScanner10 { "[-disconnect] [-enableGATTPing] [-count <number>] [-single] [-show_update_events] [-quiet] "+ "[-resetEachCon connectionCount] "+ "(-mac <device_address>)* (-wl <device_address>)* "+ - "[-passkey <digits>] [-seclevel <int>]" + + "[-seclevel <int>] [-iocap <int>] [-passkey <digits>]" + "[-charid <uuid>] [-charval <byte-val>]"+ "[-verbose] [-debug] "+ "[-dbt_verbose true|false] "+ diff --git a/java/direct_bt/tinyb/DBTDevice.java b/java/direct_bt/tinyb/DBTDevice.java index 425472ac..2895f123 100644 --- a/java/direct_bt/tinyb/DBTDevice.java +++ b/java/direct_bt/tinyb/DBTDevice.java @@ -47,6 +47,7 @@ import org.tinyb.EIRDataTypeSet; import org.tinyb.GATTCharacteristicListener; import org.tinyb.HCIStatusCode; import org.tinyb.PairingMode; +import org.tinyb.SMPIOCapability; import org.tinyb.SMPPairingState; public class DBTDevice extends DBTObject implements BluetoothDevice @@ -334,16 +335,34 @@ public class DBTDevice extends DBTObject implements BluetoothDevice } @Override - public final void setSecurityLevel(final BTSecurityLevel sec_level) { - setSecurityLevelImpl(sec_level.value); + public final boolean setConnSecurityLevel(final BTSecurityLevel sec_level, final boolean blocking) { + return setConnSecurityLevelImpl(sec_level.value, blocking); } - private final native void setSecurityLevelImpl(final byte sec_level); + private final native boolean setConnSecurityLevelImpl(final byte sec_level, final boolean blocking); @Override - public final BTSecurityLevel getCurrentSecurityLevel() { - return BTSecurityLevel.get( getCurrentSecurityLevelImpl() ); + public final BTSecurityLevel getConnSecurityLevel() { + return BTSecurityLevel.get( getConnSecurityLevelImpl() ); } - private final native byte getCurrentSecurityLevelImpl(); + private final native byte getConnSecurityLevelImpl(); + + @Override + public final boolean setConnIOCapability(final SMPIOCapability io_cap, final boolean blocking) { + return setConnIOCapabilityImpl(io_cap.value, blocking); + } + private final native boolean setConnIOCapabilityImpl(final byte io_cap, final boolean blocking); + + @Override + public final boolean setConnSecurity(final BTSecurityLevel sec_level, final SMPIOCapability io_cap, final boolean blocking) { + return setConnSecurityImpl(sec_level.value, io_cap.value, blocking); + } + private final native boolean setConnSecurityImpl(final byte sec_level, final byte io_cap, final boolean blocking); + + @Override + public final SMPIOCapability getConnIOCapability() { + return SMPIOCapability.get( getConnIOCapabilityImpl() ); + } + private final native byte getConnIOCapabilityImpl(); @Override public HCIStatusCode setPairingPasskey(final int passkey) { diff --git a/java/jni/direct_bt/DBTDevice.cxx b/java/jni/direct_bt/DBTDevice.cxx index d691f4bb..ec4e6676 100644 --- a/java/jni/direct_bt/DBTDevice.cxx +++ b/java/jni/direct_bt/DBTDevice.cxx @@ -379,29 +379,68 @@ jbyte Java_direct_1bt_tinyb_DBTDevice_connectLEImpl1(JNIEnv *env, jobject obj, } -void Java_direct_1bt_tinyb_DBTDevice_setSecurityLevelImpl(JNIEnv *env, jobject obj, jbyte jsec_level) { +jboolean Java_direct_1bt_tinyb_DBTDevice_setConnSecurityLevelImpl(JNIEnv *env, jobject obj, jbyte jsec_level, jboolean jblocking) { try { DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); - device->setSecurityLevel( getBTSecurityLevel( static_cast<uint8_t>(jsec_level) ) ); + return device->setConnSecurityLevel( getBTSecurityLevel( static_cast<uint8_t>(jsec_level) ), JNI_TRUE == jblocking ); } catch(...) { rethrow_and_raise_java_exception(env); } + return JNI_FALSE; } -jbyte Java_direct_1bt_tinyb_DBTDevice_getCurrentSecurityLevelImpl(JNIEnv *env, jobject obj) { +jbyte Java_direct_1bt_tinyb_DBTDevice_getConnSecurityLevelImpl(JNIEnv *env, jobject obj) { try { DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); - return number( device->getCurrentSecurityLevel() ); + return number( device->getConnSecurityLevel() ); } catch(...) { rethrow_and_raise_java_exception(env); } return number( BTSecurityLevel::UNSET ); } +jboolean Java_direct_1bt_tinyb_DBTDevice_setConnIOCapabilityImpl(JNIEnv *env, jobject obj, jbyte jio_cap, jboolean jblocking) { + try { + DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); + JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + + return device->setConnIOCapability( getSMPIOCapability( static_cast<uint8_t>(jio_cap) ), JNI_TRUE == jblocking ); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return JNI_FALSE; +} + +jboolean Java_direct_1bt_tinyb_DBTDevice_setConnSecurityImpl(JNIEnv *env, jobject obj, jbyte jsec_level, jbyte jio_cap, jboolean jblocking) { + try { + DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); + JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + + return device->setConnSecurity( getBTSecurityLevel( static_cast<uint8_t>(jsec_level) ), + getSMPIOCapability( static_cast<uint8_t>(jio_cap) ), + JNI_TRUE == jblocking ); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return JNI_FALSE; +} + +jbyte Java_direct_1bt_tinyb_DBTDevice_getConnIOCapabilityImpl(JNIEnv *env, jobject obj) { + try { + DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); + JavaGlobalObj::check(device->getJavaObject(), E_FILE_LINE); + + return number( device->getConnIOCapability() ); + } catch(...) { + rethrow_and_raise_java_exception(env); + } + return number( SMPIOCapability::UNSET ); +} + jbyte Java_direct_1bt_tinyb_DBTDevice_getPairingModeImpl(JNIEnv *env, jobject obj) { try { DBTDevice *device = getJavaUplinkObject<DBTDevice>(env, obj); diff --git a/java/org/tinyb/BluetoothDevice.java b/java/org/tinyb/BluetoothDevice.java index 34b3ea93..53355a51 100644 --- a/java/org/tinyb/BluetoothDevice.java +++ b/java/org/tinyb/BluetoothDevice.java @@ -194,18 +194,76 @@ public interface BluetoothDevice extends BluetoothObject boolean pair() throws BluetoothException; /** - * Set the overriding security level used at device connection. + * Set the {@link BTSecurityLevel} used to connect to this device. + * <p> + * Method returns false if this device has already being connected, + * or {@link #connectLE(short, short, short, short, short, short) connectLE} or {@link #connect()} has been issued already. + * </p> + * <p> + * To ensure consistent no authentication setup,<br> + * implementation will set {@link SMPIOCapability#NO_INPUT_NO_OUTPUT} if sec_level <= {@link BTSecurityLevel#ENC_ONLY}<br> + * and {@link #setConnIOCapability(SMPIOCapability, boolean)} not used. + * </p> + * @param sec_level {@link BTSecurityLevel} to be applied + * @param blocking if true, blocks until previous {@link SMPIOCapability} setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. + * @return + * @since 2.1.0 + * @implNote not implemented in tinyb.dbus + */ + boolean setConnSecurityLevel(final BTSecurityLevel sec_level, boolean blocking); + + /** + * Return the {@link BTSecurityLevel}, determined when connection is established. + * @since 2.1.0 + * @implNote not implemented in tinyb.dbus + */ + BTSecurityLevel getConnSecurityLevel(); + + /** + * Sets the given {@link SMPIOCapability} used to connect to this device. + * <p> + * Method returns false if operation fails, this device has already being connected, + * or {@link #connectLE(short, short, short, short, short, short) connectLE} or {@link #connect()} has been issued already. + * </p> + * <p> + * The {@link SMPIOCapability} value will be reset to its previous value when connection is completed or failed. + * </p> + * @param io_cap {@link SMPIOCapability} to be applied + * @param blocking if true, blocks until previous {@link SMPIOCapability} setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. + * @since 2.1.0 + * @implNote not implemented in tinyb.dbus + */ + boolean setConnIOCapability(final SMPIOCapability io_cap, final boolean blocking); + + /** + * Sets the given {@link BTSecurityLevel} and {@link SMPIOCapability} used to connect to this device. + * <p> + * Method returns false if operation fails, this device has already being connected, + * or {@link #connectLE(short, short, short, short, short, short) connectLE} or {@link #connect()} has been issued already. + * </p> + * <p> + * The {@link SMPIOCapability} value will be reset to its previous value when connection is completed or failed. + * </p> + * @param sec_level {@link BTSecurityLevel} to be applied + * @param io_cap {@link SMPIOCapability} to be applied + * @param blocking if true, blocks until previous {@link SMPIOCapability} setting is completed, + * i.e. until connection has been completed or failed. + * Otherwise returns immediately with false if previous connection result is still pending. * @since 2.1.0 * @implNote not implemented in tinyb.dbus */ - void setSecurityLevel(final BTSecurityLevel sec_level); + boolean setConnSecurity(final BTSecurityLevel sec_level, final SMPIOCapability io_cap, final boolean blocking); /** - * Return the currently set security level. + * Return the {@link SMPIOCapability} value, determined when connection is established. * @since 2.1.0 * @implNote not implemented in tinyb.dbus */ - BTSecurityLevel getCurrentSecurityLevel(); + SMPIOCapability getConnIOCapability(); /** * Method sets the given passkey entry, see {@link PairingMode#PASSKEY_ENTRY_ini}. diff --git a/java/tinyb/dbus/DBusDevice.java b/java/tinyb/dbus/DBusDevice.java index e3ade052..526abe62 100644 --- a/java/tinyb/dbus/DBusDevice.java +++ b/java/tinyb/dbus/DBusDevice.java @@ -45,6 +45,7 @@ import org.tinyb.BluetoothUtils; import org.tinyb.GATTCharacteristicListener; import org.tinyb.HCIStatusCode; import org.tinyb.PairingMode; +import org.tinyb.SMPIOCapability; import org.tinyb.SMPPairingState; public class DBusDevice extends DBusObject implements BluetoothDevice @@ -109,10 +110,19 @@ public class DBusDevice extends DBusObject implements BluetoothDevice public native boolean pair() throws BluetoothException; @Override - public final void setSecurityLevel(final BTSecurityLevel sec_level) { } // FIXME + public final boolean setConnSecurityLevel(final BTSecurityLevel sec_level, final boolean blocking) { return false; } // FIXME @Override - public final BTSecurityLevel getCurrentSecurityLevel() { return BTSecurityLevel.UNSET; } // FIXME + public final BTSecurityLevel getConnSecurityLevel() { return BTSecurityLevel.UNSET; } // FIXME + + @Override + public final boolean setConnIOCapability(final SMPIOCapability io_cap, final boolean blocking) { return false; } // FIXME + + @Override + public final boolean setConnSecurity(final BTSecurityLevel sec_level, final SMPIOCapability io_cap, final boolean blocking) { return false; } // FIXME + + @Override + public final SMPIOCapability getConnIOCapability() { return SMPIOCapability.UNSET; } // FIXME @Override public HCIStatusCode setPairingPasskey(final int passkey) { return HCIStatusCode.INTERNAL_FAILURE; } diff --git a/src/direct_bt/DBTAdapter.cpp b/src/direct_bt/DBTAdapter.cpp index bcc23d17..8d8da919 100644 --- a/src/direct_bt/DBTAdapter.cpp +++ b/src/direct_bt/DBTAdapter.cpp @@ -201,7 +201,9 @@ bool DBTAdapter::validateDevInfo() noexcept { ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::CONNECT_FAILED, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvConnectFailedHCI)) && ok; ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DEVICE_DISCONNECTED, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvDeviceDisconnectedHCI)) && ok; ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::DEVICE_FOUND, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvDeviceFoundHCI)) && ok; - ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::LE_REMOTE_USER_FEATURES, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvLERemoteUserFeaturesHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_LE_REMOTE_USR_FEATURES, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvHCILERemoteUserFeaturesHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_ENC_CHANGED, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvHCIEncryptionChangedHCI)) && ok; + ok = hci.addMgmtEventCallback(MgmtEvent::Opcode::HCI_ENC_KEY_REFRESH_COMPLETE, jau::bindMemberFunc(this, &DBTAdapter::mgmtEvHCIEncryptionKeyRefreshCompleteHCI)) && ok; if( !ok ) { ERR_PRINT("Could not add all required MgmtEventCallbacks to HCIHandler: %s of %s", hci.toString().c_str(), toString().c_str()); @@ -309,6 +311,12 @@ void DBTAdapter::poweredOff() noexcept { hci.setCurrentScanType(ScanType::NONE); currentMetaScanType = ScanType::NONE; + // ensure all hci states are reset. + hci.clearAllStates(); + + io_capability_defaultval = SMPIOCapability::UNSET; + io_capability_device_ptr = nullptr; + DBG_PRINT("DBTAdapter::poweredOff: XXX"); } @@ -332,20 +340,74 @@ std::shared_ptr<NameAndShortName> DBTAdapter::setLocalName(const std::string &na } bool DBTAdapter::setDiscoverable(bool value) noexcept { - AdapterSetting current_settings; + AdapterSetting current_settings { AdapterSetting::NONE } ; return MgmtStatus::SUCCESS == mgmt.setDiscoverable(dev_id, value ? 0x01 : 0x00, 10 /* timeout seconds */, current_settings); } bool DBTAdapter::setBondable(bool value) noexcept { - AdapterSetting current_settings; + AdapterSetting current_settings { AdapterSetting::NONE } ; return mgmt.setMode(dev_id, MgmtCommand::Opcode::SET_BONDABLE, value ? 1 : 0, current_settings); } bool DBTAdapter::setPowered(bool value) noexcept { - AdapterSetting current_settings; + AdapterSetting current_settings { AdapterSetting::NONE } ; return mgmt.setMode(dev_id, MgmtCommand::Opcode::SET_POWERED, value ? 1 : 0, current_settings); } +bool DBTAdapter::setConnIOCapability(const DBTDevice & device, const SMPIOCapability io_cap, const bool blocking) noexcept { + const DBTDevice * exp_null_device = nullptr; // C++11, exp as value since C++20 + const uint32_t timeout_ms = 10000; // FIXME: Configurable? + const uint32_t poll_period_ms = 125; + uint32_t td = 0; + while( !io_capability_device_ptr.compare_exchange_strong(exp_null_device, &device) ) { + if( blocking ) { + if( timeout_ms <= td ) { + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(poll_period_ms)); + td += poll_period_ms; + } else { + return false; + } + } + SMPIOCapability o; + const bool res = mgmt.setIOCapability(dev_id, io_cap, o); + DBG_PRINT("DBTAdapter::setConnIOCapability: result %d: %s -> %s, %s", + res, getSMPIOCapabilityString(o).c_str(), getSMPIOCapabilityString(io_cap).c_str(), + device.toString(false).c_str()); + if( res ) { + io_capability_defaultval = o; + return true; + } else { + io_capability_device_ptr = nullptr; + return false; + } +} + +bool DBTAdapter::resetConnIOCapability(const DBTDevice & device) noexcept { + SMPIOCapability pre_io_cap { SMPIOCapability::UNSET }; + return resetConnIOCapability(device, pre_io_cap); +} + +bool DBTAdapter::resetConnIOCapability(const DBTDevice & device, SMPIOCapability& pre_io_cap) noexcept { + if( nullptr != io_capability_device_ptr && device == *io_capability_device_ptr ) { + const SMPIOCapability v = io_capability_defaultval; + io_capability_defaultval = SMPIOCapability::UNSET; + io_capability_device_ptr = nullptr; + SMPIOCapability o; + const bool res = mgmt.setIOCapability(dev_id, v, o); + DBG_PRINT("DBTAdapter::resetConnIOCapability: result %d: %s -> %s, %s", + res, getSMPIOCapabilityString(o).c_str(), getSMPIOCapabilityString(v).c_str(), + device.toString(false).c_str()); + if( res ) { + pre_io_cap = o; + } + return res; + } else { + return false; + } +} + HCIStatusCode DBTAdapter::reset() noexcept { if( !isValid() ) { ERR_PRINT("DBTAdapter::reset(): Adapter invalid: %s, %s", aptrHexString(this).c_str(), toString().c_str()); @@ -704,6 +766,7 @@ void DBTAdapter::removeDevice(DBTDevice & 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()); + resetConnIOCapability(device); removeConnectedDevice(device); // usually done in DBTAdapter::mgmtEvDeviceDisconnectedHCI removeDiscoveredDevice(device); // usually done in DBTAdapter::mgmtEvDeviceDisconnectedHCI WORDY_PRINT("DBTAdapter::removeDevice: End %s", toString(false).c_str()); @@ -947,6 +1010,8 @@ bool DBTAdapter::mgmtEvDeviceConnectedHCI(std::shared_ptr<MgmtEvent> e) noexcept new_connect = 3; } + const SMPIOCapability io_cap_conn = mgmt.getIOCapability(dev_id); + EIRDataType updateMask = device->update(ad_report); if( addConnectedDevice(device) ) { // track device, if not done yet if( 0 == new_connect ) { @@ -970,7 +1035,7 @@ bool DBTAdapter::mgmtEvDeviceConnectedHCI(std::shared_ptr<MgmtEvent> e) noexcept device->toString().c_str()); } - device->notifyConnected(device, event.getHCIHandle()); + device->notifyConnected(device, event.getHCIHandle(), io_cap_conn); int i=0; jau::for_each_cow(statusListenerList, [&](std::shared_ptr<AdapterStatusListener> &l) { @@ -1004,6 +1069,7 @@ bool DBTAdapter::mgmtEvConnectFailedHCI(std::shared_ptr<MgmtEvent> e) noexcept { dev_id, event.toString().c_str(), jau::uint16HexString(handle).c_str(), device->toString().c_str()); + resetConnIOCapability(*device); device->notifyDisconnected(); removeConnectedDevice(*device); @@ -1028,8 +1094,8 @@ bool DBTAdapter::mgmtEvConnectFailedHCI(std::shared_ptr<MgmtEvent> e) noexcept { return true; } -bool DBTAdapter::mgmtEvLERemoteUserFeaturesHCI(std::shared_ptr<MgmtEvent> e) noexcept { - const MgmtEvtLERemoteUserFeatures &event = *static_cast<const MgmtEvtLERemoteUserFeatures *>(e.get()); +bool DBTAdapter::mgmtEvHCILERemoteUserFeaturesHCI(std::shared_ptr<MgmtEvent> e) noexcept { + const MgmtEvtHCILERemoteUserFeatures &event = *static_cast<const MgmtEvtHCILERemoteUserFeatures *>(e.get()); std::shared_ptr<DBTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); if( nullptr != device ) { @@ -1045,6 +1111,31 @@ bool DBTAdapter::mgmtEvLERemoteUserFeaturesHCI(std::shared_ptr<MgmtEvent> e) noe return true; } +bool DBTAdapter::mgmtEvHCIEncryptionChangedHCI(std::shared_ptr<MgmtEvent> e) noexcept { + const MgmtEvtHCIEncryptionChanged &event = *static_cast<const MgmtEvtHCIEncryptionChanged *>(e.get()); + + std::shared_ptr<DBTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + device->updatePairingState(device, SMPPairingState::PROCESS_COMPLETED, e); + } else { + WORDY_PRINT("DBTAdapter::EventHCI:EncryptionChanged(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} +bool DBTAdapter::mgmtEvHCIEncryptionKeyRefreshCompleteHCI(std::shared_ptr<MgmtEvent> e) noexcept { + const MgmtEvtHCIEncryptionKeyRefreshComplete &event = *static_cast<const MgmtEvtHCIEncryptionKeyRefreshComplete *>(e.get()); + + std::shared_ptr<DBTDevice> device = findConnectedDevice(event.getAddress(), event.getAddressType()); + if( nullptr != device ) { + device->updatePairingState(device, SMPPairingState::PROCESS_COMPLETED, e); + } else { + WORDY_PRINT("DBTAdapter::EventHCI:EncryptionKeyRefreshComplete(dev_id %d): Device not tracked: %s", + dev_id, event.toString().c_str()); + } + return true; +} + bool DBTAdapter::mgmtEvDeviceDisconnectedHCI(std::shared_ptr<MgmtEvent> e) noexcept { const MgmtEvtDeviceDisconnected &event = *static_cast<const MgmtEvtDeviceDisconnected *>(e.get()); @@ -1059,6 +1150,7 @@ bool DBTAdapter::mgmtEvDeviceDisconnectedHCI(std::shared_ptr<MgmtEvent> e) noexc dev_id, event.toString().c_str(), jau::uint16HexString(event.getHCIHandle()).c_str(), device->toString().c_str()); + resetConnIOCapability(*device); device->notifyDisconnected(); removeConnectedDevice(*device); @@ -1215,15 +1307,8 @@ bool DBTAdapter::mgmtEvUserConfirmRequestMgmt(std::shared_ptr<MgmtEvent> e) noex event.toString().c_str()); return true; } - // FIXME: Pass confirm_hint and value - const SMPPairingState pstate = SMPPairingState::NUMERIC_COMPARE_EXPECTED; - - DBG_PRINT("DBTAdapter:mgmt:SMP: dev_id %d: address[%s, %s]: state %s, %s", - dev_id, event.getAddress().toString().c_str(), getBDAddressTypeString(event.getAddressType()).c_str(), - getSMPPairingStateString(pstate).c_str(), - event.toString().c_str()); - - updatePairingState(device, pstate, e->getTimestamp()); + // FIXME: Pass confirm_hint and value? + device->updatePairingState(device, SMPPairingState::NUMERIC_COMPARE_EXPECTED, e); return true; } bool DBTAdapter::mgmtEvUserPasskeyRequestMgmt(std::shared_ptr<MgmtEvent> e) noexcept { @@ -1236,29 +1321,10 @@ bool DBTAdapter::mgmtEvUserPasskeyRequestMgmt(std::shared_ptr<MgmtEvent> e) noex event.toString().c_str()); return true; } - const SMPPairingState pstate = SMPPairingState::PASSKEY_EXPECTED; - - DBG_PRINT("DBTAdapter:mgmt:SMP: dev_id %d: address[%s, %s]: state %s, %s", - dev_id, event.getAddress().toString().c_str(), getBDAddressTypeString(event.getAddressType()).c_str(), - getSMPPairingStateString(pstate).c_str(), - event.toString().c_str()); - - updatePairingState(device, pstate, e->getTimestamp()); + device->updatePairingState(device, SMPPairingState::PASSKEY_EXPECTED, e); return true; } -void DBTAdapter::updatePairingState(std::shared_ptr<DBTDevice> device, const SMPPairingState pstate, uint64_t timestamp) noexcept -{ - PairingMode pmode; - if( device->updatePairingState_locked(pstate, pmode) ) { - sendDevicePairingState(device, pstate, pmode, timestamp); - } else { - WORDY_PRINT("DBTAdapter::updatePairingState: dev_id %d: Unchanged: state %s, %s", dev_id, - getSMPPairingStateString(pstate).c_str(), - device->toString().c_str()); - } -} - bool DBTAdapter::hciSMPMsgCallback(const EUI48& address, BDAddressType addressType, std::shared_ptr<const SMPPDUMsg> msg, const HCIACLData::l2cap_frame& source) noexcept { std::shared_ptr<DBTDevice> device = findConnectedDevice(address, addressType); diff --git a/src/direct_bt/DBTDevice.cpp b/src/direct_bt/DBTDevice.cpp index 8c5b42a8..c52a9bf3 100644 --- a/src/direct_bt/DBTDevice.cpp +++ b/src/direct_bt/DBTDevice.cpp @@ -142,7 +142,8 @@ std::string DBTDevice::toString(bool includeDiscoveredServices) const noexcept { std::string out("Device[address["+getAddressString()+", "+getBDAddressTypeString(getAddressType())+leaddrtype+"], name['"+name+ "'], age[total "+std::to_string(t0-ts_creation)+", ldisc "+std::to_string(t0-ts_last_discovery)+", lup "+std::to_string(t0-ts_last_update)+ "]ms, connected["+std::to_string(allowDisconnect)+"/"+std::to_string(isConnected)+", handle "+jau::uint16HexString(hciConnHandle)+ - "], security "+getBTSecurityLevelString(pairing_data.sec_level).c_str()+", pairing "+getPairingModeString(pairing_data.mode).c_str()+", rssi "+std::to_string(getRSSI())+ + ", sec[lvl "+getBTSecurityLevelString(pairing_data.sec_level_conn).c_str()+", io "+getSMPIOCapabilityString(pairing_data.ioCap_conn).c_str()+ + ", pairing "+getPairingModeString(pairing_data.mode).c_str()+"]], rssi "+std::to_string(getRSSI())+ ", tx-power "+std::to_string(tx_power)+ ", appearance "+jau::uint16HexString(static_cast<uint16_t>(appearance))+" ("+getAppearanceCatString(appearance)+ "), "+msdstr+", "+javaObjectToString()+"]"); @@ -388,14 +389,17 @@ HCIStatusCode DBTDevice::connectDefault() } } -void DBTDevice::notifyConnected(std::shared_ptr<DBTDevice> sthis, const uint16_t handle) noexcept { +void DBTDevice::notifyConnected(std::shared_ptr<DBTDevice> sthis, const uint16_t handle, const SMPIOCapability io_cap) noexcept { // coming from connected callback, update state and spawn-off connectGATT in background if appropriate (LE) - DBG_PRINT("DBTDevice::notifyConnected: handle %s -> %s, %s", - jau::uint16HexString(hciConnHandle).c_str(), jau::uint16HexString(handle).c_str(), toString().c_str()); + DBG_PRINT("DBTDevice::notifyConnected: handle %s -> %s, io %s -> %s, %s", + jau::uint16HexString(hciConnHandle).c_str(), jau::uint16HexString(handle).c_str(), + getSMPIOCapabilityString(pairing_data.ioCap_conn).c_str(), getSMPIOCapabilityString(io_cap).c_str(), + toString(false).c_str()); clearSMPStates(); allowDisconnect = true; isConnected = true; hciConnHandle = handle; + pairing_data.ioCap_conn = io_cap; (void)sthis; // not used yet } @@ -411,13 +415,20 @@ void DBTDevice::notifyLEFeatures(std::shared_ptr<DBTDevice> sthis, const LEFeatu } void DBTDevice::processL2CAPSetup(std::shared_ptr<DBTDevice> sthis) { - DBG_PRINT("DBTDevice::processL2CAPSetup: %s", toString().c_str()); + // FIXME: Deadlocks SMP process while @ l2cap_att.setBTSecurityLevel() -> SMP + **MGMT::PASSKEY_REQ** -> l2cap_att.connect()-completion, + // i.e. l2cap_att.setBTSecurityLevel() (or its connect() process security callbacks. + // const std::lock_guard<std::mutex> lock(mtx_pairing); // RAII-style acquire and relinquish via destructor + (void)sthis; + DBG_PRINT("DBTDevice::processL2CAPSetup: Start %s", toString(false).c_str()); if( isLEAddressType() && !l2cap_att.isOpen() ) { const bool responderLikesEncryption = pairing_data.res_requested_sec || isLEFeaturesBitSet(le_features, LEFeatures::LE_Encryption); const BTSecurityLevel sec_level_user = pairing_data.sec_level_user; - BTSecurityLevel sec_level = pairing_data.sec_level; + const SMPIOCapability io_cap = pairing_data.ioCap_conn; + BTSecurityLevel sec_level { BTSecurityLevel::UNSET }; if( BTSecurityLevel::UNSET != sec_level_user ) { sec_level = sec_level_user; + } else if( SMPIOCapability::NO_INPUT_NO_OUTPUT == io_cap ) { + sec_level = BTSecurityLevel::ENC_ONLY; // no auth w/o I/O } else { if( responderLikesEncryption && adapter.hasSecureConnections() ) { sec_level = BTSecurityLevel::ENC_AUTH_FIPS; @@ -427,25 +438,26 @@ void DBTDevice::processL2CAPSetup(std::shared_ptr<DBTDevice> sthis) { sec_level = BTSecurityLevel::NONE; } } - const bool l2cap_open = l2cap_att.open(*this); - const bool l2cap_sec = l2cap_open && l2cap_att.setBTSecurityLevel(sec_level); // initiates hciSMPMsgCallback() if sec_level > BT_SECURITY_LOW + pairing_data.sec_level_conn = sec_level; + const bool l2cap_open = l2cap_att.open(*this, sec_level); // initiates hciSMPMsgCallback() if sec_level > BT_SECURITY_LOW + const bool l2cap_auth = l2cap_open && BTSecurityLevel::ENC_ONLY < sec_level; #if SMP_SUPPORTED_BY_OS - const bool smp_sec = connectSMP(sthis, sec_level); + const bool smp_auth = connectSMP(sthis, sec_level) && BTSecurityLevel::ENC_ONLY < sec_level; #else - const bool smp_sec = false; + const bool smp_auth = false; #endif - DBG_PRINT("DBTDevice::processL2CAPSetup: sec_level %s, connect[smp %d, l2cap[open %d, sec %d]], %s", - getBTSecurityLevelString(sec_level).c_str(), smp_sec, l2cap_open, l2cap_sec, toString().c_str()); + DBG_PRINT("DBTDevice::processL2CAPSetup: lvl %s, io %s, connect[smp auth %d, l2cap[open %d, auth %d]]", + getBTSecurityLevelString(sec_level).c_str(), getSMPIOCapabilityString(io_cap).c_str(), + smp_auth, l2cap_open, l2cap_auth); + + adapter.resetConnIOCapability(*this); + if( !l2cap_open ) { + pairing_data.sec_level_conn = BTSecurityLevel::NONE; disconnect(HCIStatusCode::INTERNAL_FAILURE); - } else if( !l2cap_sec && !smp_sec ) { - // No security and hence no SMP dialogue, i.e. hciSMPMsgCallback(): - pairing_data.sec_level = BTSecurityLevel::NONE; - processDeviceReady(sthis, jau::getCurrentMilliseconds()); - } else { - pairing_data.sec_level = sec_level; } } + DBG_PRINT("DBTDevice::processL2CAPSetup: End %s", toString(false).c_str()); } void DBTDevice::processDeviceReady(std::shared_ptr<DBTDevice> sthis, const uint64_t timestamp) { @@ -457,9 +469,15 @@ void DBTDevice::processDeviceReady(std::shared_ptr<DBTDevice> sthis, const uint6 } } -bool DBTDevice::updatePairingState_locked(SMPPairingState state, PairingMode& current_mode) noexcept { +bool DBTDevice::updatePairingState(std::shared_ptr<DBTDevice> sthis, SMPPairingState state, std::shared_ptr<MgmtEvent> evt) noexcept { const std::lock_guard<std::mutex> lock(mtx_pairing); // RAII-style acquire and relinquish via destructor PairingMode mode = pairing_data.mode; + bool is_device_ready = false; + + DBG_PRINT("DBTDevice::updatePairingState.0: state %s -> %s, %s, %s", + getSMPPairingStateString(pairing_data.state).c_str(), getSMPPairingStateString(state).c_str(), + evt->toString().c_str(), toString(false).c_str()); + if( pairing_data.state != state ) { // Potentially force update PairingMode by forced state change, assuming being the initiator. // FIXME: Initiator and responder role might need more specific determination and documentation. @@ -473,15 +491,39 @@ bool DBTDevice::updatePairingState_locked(SMPPairingState state, PairingMode& cu case SMPPairingState::OOB_EXPECTED: mode = PairingMode::OUT_OF_BAND; break; + case SMPPairingState::PROCESS_COMPLETED: + if( BTSecurityLevel::ENC_ONLY >= pairing_data.sec_level_conn ) { + // no auth, done + mode = PairingMode::JUST_WORKS; + is_device_ready = true; + } else { + // requires SMP auth: reset, no change + state = pairing_data.state; + } + break; default: // nop break; } + DBG_PRINT("DBTDevice::updatePairingState.1: state %s -> %s, mode %s -> %s, ready %d", + getSMPPairingStateString(pairing_data.state).c_str(), getSMPPairingStateString(state).c_str(), + getPairingModeString(pairing_data.mode).c_str(), getPairingModeString(mode).c_str(), is_device_ready); + } + + if( pairing_data.state != state ) { pairing_data.mode = mode; pairing_data.state = state; - current_mode = mode; + + adapter.sendDevicePairingState(sthis, state, mode, evt->getTimestamp()); + + if( is_device_ready ) { + std::thread dc(&DBTDevice::processDeviceReady, this, sthis, evt->getTimestamp()); // @suppress("Invalid arguments") + dc.detach(); + } + DBG_PRINT("DBTDevice::updatePairingState.2: Complete: state %s", getSMPPairingStateString(state).c_str()); return true; + } else { + DBG_PRINT("DBTDevice::updatePairingState.3: Unchanged: state %s", getSMPPairingStateString(state).c_str()); } - current_mode = mode; return false; } @@ -499,6 +541,7 @@ void DBTDevice::hciSMPMsgCallback(std::shared_ptr<DBTDevice> sthis, std::shared_ switch( opc ) { case SMPPDUMsg::Opcode::PAIRING_FAILED: { +#if 0 pmode = PairingMode::NONE; pstate = SMPPairingState::FAILED; pairing_data.res_requested_sec = false; @@ -510,11 +553,14 @@ void DBTDevice::hciSMPMsgCallback(std::shared_ptr<DBTDevice> sthis, std::shared_ pairing_data.sec_level = sec_level; } const bool l2cap_close = l2cap_att.close(); - const bool l2cap_open = l2cap_att.open(*this); + const bool l2cap_open = l2cap_att.open(*this, sec_level); is_device_ready = l2cap_open; DBG_PRINT("DBTDevice:hci:SMP: l2cap ATT reopen: ready %d, sec_level %s, l2cap[close %d, open %d], %s", is_device_ready, getBTSecurityLevelString(sec_level).c_str(), l2cap_close, l2cap_open, toString(false).c_str()); +#else + pstate = SMPPairingState::FAILED; +#endif } break; @@ -600,10 +646,11 @@ void DBTDevice::hciSMPMsgCallback(std::shared_ptr<DBTDevice> sthis, std::shared_ return; } - DBG_PRINT("DBTDevice:hci:SMP: address[%s, %s]: state %s -> %s, mode %s -> %s, %s, %s", + DBG_PRINT("DBTDevice:hci:SMP: address[%s, %s]: state %s -> %s, mode %s -> %s, ready %d, %s, %s", address.toString().c_str(), getBDAddressTypeString(addressType).c_str(), getSMPPairingStateString(old_pstate).c_str(), getSMPPairingStateString(pstate).c_str(), getPairingModeString(old_pmode).c_str(), getPairingModeString(pmode).c_str(), + is_device_ready, msg->toString().c_str(), source.toString().c_str()); pairing_data.mode = pmode; @@ -617,6 +664,46 @@ void DBTDevice::hciSMPMsgCallback(std::shared_ptr<DBTDevice> sthis, std::shared_ } } +bool DBTDevice::setConnSecurityLevel(const BTSecurityLevel sec_level, const bool blocking) noexcept { + DBG_PRINT("DBTDevice::setSecurityLevel: %s -> %s, %s", + getBTSecurityLevelString(pairing_data.sec_level_user).c_str(), + getBTSecurityLevelString(sec_level).c_str(), toString(false).c_str()); + + if( !isValid() || isConnected || allowDisconnect ) { + return false; + } + pairing_data.sec_level_user = sec_level; + + if( BTSecurityLevel::ENC_ONLY >= sec_level && !adapter.isConnIOCapabilitySet() ) + { + // Adjust SMPIOCapability to match the no_auth BTSecurityLevel <= BTSecurityLevel::ENC_ONLY, + // if connect hasn't been issued and setIOCapability() not used yet. + return setConnIOCapability( SMPIOCapability::NO_INPUT_NO_OUTPUT, blocking); + } else { + return true; + } +} + +bool DBTDevice::setConnIOCapability(const SMPIOCapability io_cap, const bool blocking) noexcept { + if( !isValid() || isConnected || allowDisconnect ) { + return false; + } + return adapter.setConnIOCapability(*this, io_cap, blocking); +} + +bool DBTDevice::setConnSecurity(const BTSecurityLevel sec_level, const SMPIOCapability io_cap, const bool blocking) noexcept { + DBG_PRINT("DBTDevice::setConnSecurity: %s -> %s, %s, %s", + getBTSecurityLevelString(pairing_data.sec_level_user).c_str(), + getBTSecurityLevelString(sec_level).c_str(), + getSMPIOCapabilityString(io_cap).c_str(), toString(false).c_str()); + + if( !isValid() || isConnected || allowDisconnect ) { + return false; + } + pairing_data.sec_level_user = sec_level; + return adapter.setConnIOCapability(*this, io_cap, blocking); +} + HCIStatusCode DBTDevice::setPairingPasskey(const uint32_t passkey) noexcept { const std::lock_guard<std::mutex> lock(mtx_pairing); // RAII-style acquire and relinquish via destructor @@ -668,9 +755,12 @@ HCIStatusCode DBTDevice::setPairingNumericComparison(const bool positive) noexce void DBTDevice::clearSMPStates() noexcept { const std::lock_guard<std::mutex> lock(mtx_pairing); // RAII-style acquire and relinquish via destructor - pairing_data.sec_level = BTSecurityLevel::UNSET; - pairing_data.mode = PairingMode::NONE; + pairing_data.ioCap_conn=SMPIOCapability::UNSET; + pairing_data.sec_level_conn = BTSecurityLevel::UNSET; + pairing_data.sec_level_user = BTSecurityLevel::UNSET; + pairing_data.state = SMPPairingState::NONE; + pairing_data.mode = PairingMode::NONE; pairing_data.res_requested_sec = false; pairing_data.authReqs_resp = SMPAuthReqs::NONE; @@ -981,5 +1071,6 @@ exit: } void DBTDevice::remove() noexcept { + clearSMPStates(); adapter.removeDevice(*this); } |