/*
* Author: Sven Gothel
* 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.
*/
#ifndef DBT_ADAPTER_HPP_
#define DBT_ADAPTER_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
#include "DBTTypes.hpp"
#include "DBTDevice.hpp"
#include "HCIHandler.hpp"
#include "DBTManager.hpp"
namespace direct_bt {
class DBTAdapter; // forward
/**
* {@link DBTAdapter} status listener for {@link DBTDevice} discovery events: Added, updated and removed;
* as well as for certain {@link DBTAdapter} events.
*
* User implementations shall return as early as possible to avoid blocking the event-handler thread.
* Especially complex mutable operations on DBTDevice or DBTAdapter should be issued off-thread!
*
*
* A listener instance may be attached to a {@link DBTAdapter} via
* {@link DBTAdapter::addStatusListener(std::shared_ptr)}.
*
*
* The listener receiver maintains a unique set of listener instances without duplicates.
*
*/
class AdapterStatusListener {
public:
/**
* Custom filter for all 'device*' notification methods,
* which will not be called if this method returns false.
*
* User may override this method to test whether the 'device*' methods shall be called
* for the given device.
*
*
* Defaults to true;
*
*/
virtual bool matchDevice(const DBTDevice & device) {
(void)device;
return true;
}
/**
* DBTAdapter setting(s) changed.
* @param adapter the adapter which settings have changed.
* @param oldmask the previous settings mask. AdapterSetting::NONE indicates the initial setting notification, see DBTAdapter::addStatusListener().
* @param newmask the new settings mask
* @param changedmask the changes settings mask
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*/
virtual void adapterSettingsChanged(DBTAdapter &adapter, const AdapterSetting oldmask, const AdapterSetting newmask,
const AdapterSetting changedmask, const uint64_t timestamp) = 0;
/**
* DBTAdapter's discovery state has changed, i.e. enabled or disabled.
* @param adapter the adapter which discovering state has changed.
* @param currentMeta the current meta ScanType
* @param changedType denotes the changed ScanType
* @param changedEnabled denotes whether the changed ScanType has been enabled or disabled
* @param keepAlive if {@code true}, the denoted changed ScanType will be re-enabled if disabled by the underlying Bluetooth implementation.
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*
* changeScanType(const ScanType current, const bool enable, const ScanType enableChanged) noexcept {
*/
virtual void discoveringChanged(DBTAdapter &adapter, const ScanType currentMeta, const ScanType changedType, const bool changedEnabled, const bool keepAlive, const uint64_t timestamp) = 0;
/**
* A DBTDevice has been newly discovered.
* @param device the found device
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*/
virtual void deviceFound(std::shared_ptr device, const uint64_t timestamp) = 0;
/**
* An already discovered DBTDevice has been updated.
* @param device the updated device
* @param updateMask the update mask of changed data
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*/
virtual void deviceUpdated(std::shared_ptr device, const EIRDataType updateMask, const uint64_t timestamp) = 0;
/**
* DBTDevice got connected
* @param device the device which has been connected, holding the new connection handle.
* @param handle the new connection handle, which has been assigned to the device already
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*/
virtual void deviceConnected(std::shared_ptr device, const uint16_t handle, const uint64_t timestamp) = 0;
/**
* DBTDevice got disconnected
* @param device the device which has been disconnected with zeroed connection handle.
* @param reason the HCIStatusCode reason for disconnection
* @param handle the disconnected connection handle, which has been unassigned from the device already
* @param timestamp the time in monotonic milliseconds when this event occurred. See BasicTypes::getCurrentMilliseconds().
*/
virtual void deviceDisconnected(std::shared_ptr device, const HCIStatusCode reason, const uint16_t handle, const uint64_t timestamp) = 0;
virtual ~AdapterStatusListener() {}
virtual std::string toString() const = 0;
/**
* Default comparison operator, merely testing for same memory reference.
*
* Specializations may override.
*
*/
virtual bool operator==(const AdapterStatusListener& rhs) const
{ return this == &rhs; }
bool operator!=(const AdapterStatusListener& rhs) const
{ return !(*this == rhs); }
};
// *************************************************
// *************************************************
// *************************************************
template class CowList
{
bool add(T);
bool remove(T);
int removeAll();
int size();
};
/**
* DBTAdapter represents one Bluetooth Controller.
*
* Controlling Environment variables:
*
* - 'direct_bt.debug.adapter.event': Debug messages about events, see debug_events
*
*
*/
class DBTAdapter : public DBTObject
{
private:
const bool debug_event;
DBTManager& mgmt;
public:
/**
* Adapter's internal temporary device id.
*
* The internal device id is constant across the adapter lifecycle,
* but may change after its destruction.
*/
const int dev_id;
private:
HCIHandler hci;
std::atomic old_settings;
std::shared_ptr adapterInfo;
std::atomic btMode = BTMode::NONE;
NameAndShortName localName;
std::atomic currentMetaScanType; // = ScanType::NONE
std::atomic keep_le_scan_alive; // = false;
std::vector> connectedDevices;
std::vector> discoveredDevices; // all discovered devices
std::vector> sharedDevices; // All active shared devices. Final holder of DBTDevice lifecycle!
jau::cow_vector> statusListenerList;
std::mutex mtx_discoveredDevices;
std::mutex mtx_connectedDevices;
std::mutex mtx_discovery;
std::mutex mtx_sharedDevices; // final mutex of all DBTDevice lifecycle
bool validateDevInfo() noexcept;
/** Returns index >= 0 if found, otherwise -1 */
static int findDeviceIdx(std::vector> & devices, EUI48 const & mac, const BDAddressType macType) noexcept;
static std::shared_ptr findDevice(std::vector> & devices, EUI48 const & mac, const BDAddressType macType) noexcept;
std::shared_ptr findDevice(std::vector> & devices, DBTDevice const & device) noexcept;
/**
* Closes all device connections, stops discovery and cleans up all references.
*
* To be called at destructor or when powered off.
*
*/
void poweredOff() noexcept;
friend std::shared_ptr DBTDevice::getSharedInstance() const noexcept;
friend std::shared_ptr DBTDevice::getConnectionInfo() noexcept;
friend HCIStatusCode DBTDevice::disconnect(const HCIStatusCode reason) noexcept;
friend void DBTDevice::remove() noexcept;
friend HCIStatusCode DBTDevice::connectLE(uint16_t interval, uint16_t window,
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 std::vector> DBTDevice::getGATTServices() noexcept;
bool addConnectedDevice(const std::shared_ptr & device) noexcept;
bool removeConnectedDevice(const DBTDevice & device) noexcept;
int disconnectAllDevices(const HCIStatusCode reason=HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION ) noexcept;
std::shared_ptr findConnectedDevice (EUI48 const & mac, const BDAddressType macType) noexcept;
bool addDiscoveredDevice(std::shared_ptr const &device) noexcept;
bool removeDiscoveredDevice(const DBTDevice & device) noexcept;
void removeDevice(DBTDevice & device) noexcept;
bool addSharedDevice(std::shared_ptr const &device) noexcept;
std::shared_ptr getSharedDevice(const DBTDevice & device) noexcept;
void removeSharedDevice(const DBTDevice & device) noexcept;
std::shared_ptr findSharedDevice (EUI48 const & mac, const BDAddressType macType) noexcept;
bool mgmtEvAdapterAddedMgmt(std::shared_ptr e) noexcept;
bool mgmtEvAdapterRemovedMgmt(std::shared_ptr e) noexcept;
bool mgmtEvNewSettingsMgmt(std::shared_ptr e) noexcept;
bool mgmtEvDeviceDiscoveringMgmt(std::shared_ptr e) noexcept;
bool mgmtEvLocalNameChangedMgmt(std::shared_ptr e) noexcept;
bool mgmtEvDeviceFoundHCI(std::shared_ptr e) noexcept;
bool mgmtEvDeviceDisconnectedMgmt(std::shared_ptr e) noexcept;
bool mgmtEvDeviceDiscoveringHCI(std::shared_ptr e) noexcept;
bool mgmtEvDeviceConnectedHCI(std::shared_ptr e) noexcept;
bool mgmtEvConnectFailedHCI(std::shared_ptr e) noexcept;
bool mgmtEvDeviceDisconnectedHCI(std::shared_ptr e) noexcept;
bool mgmtEvDeviceDiscoveringAny(std::shared_ptr e, const bool hciSourced) noexcept;
void startDiscoveryBackground() noexcept;
void checkDiscoveryState() noexcept;
void sendAdapterSettingsChanged(const AdapterSetting old_settings_, const AdapterSetting current_settings,
const uint64_t timestampMS) noexcept;
void sendAdapterSettingsChanged(AdapterStatusListener & asl,
const AdapterSetting old_settings_, const AdapterSetting current_settings,
const uint64_t timestampMS) noexcept;
void sendDeviceUpdated(std::string cause, std::shared_ptr device, uint64_t timestamp, EIRDataType updateMask) noexcept;
public:
/**
* Using the default adapter device
*
* The default adapter is either the first POWERED adapter,
* or the one with index == dev_id == 0.
*
*/
DBTAdapter() noexcept;
/**
* Using the identified adapter with given mac address.
*
* @param[in] mac address
*/
DBTAdapter(EUI48 &mac) noexcept;
/**
* Using the identified adapter with given dev_id,
* or the default adapter device if dev_id < 0.
*
* The default adapter is either the first POWERED adapter,
* or the one with index == dev_id == 0.
*
*
* @param[in] dev_id an already identified HCI device id
* or use -1 to choose the default adapter.
*/
DBTAdapter(const int dev_id) noexcept;
/**
* Releases this instance.
*/
~DBTAdapter() noexcept;
/**
* Closes this instance, usually being called by destructor or when this adapter is being removed.
*
* Renders this adapter's DBTAdapter#isValid() state to false.
*
*/
void close() noexcept;
std::string get_java_class() const noexcept override {
return java_class();
}
static std::string java_class() noexcept {
return std::string(JAVA_DBT_PACKAGE "DBTAdapter");
}
bool hasDevId() const noexcept { return 0 <= dev_id; }
/**
* Returns whether the adapter is valid, plugged in and powered.
* @return true if DBTAdapter::isValid(), HCIHandler::isOpen() and AdapterSetting::POWERED state is set.
* @see #isSuspended()
* @see #isValid()
*/
bool isPowered() const noexcept {
return isValid() && hci.isOpen() && adapterInfo->isCurrentSettingBitSet(AdapterSetting::POWERED);
}
/**
* Returns whether the adapter is suspended, i.e. valid and plugged in, but not powered.
* @return true if DBTAdapter::isValid(), HCIHandler::isOpen() and AdapterSetting::POWERED state is not set.
* @see #isPowered()
* @see #isValid()
*/
bool isSuspended() const noexcept {
return isValid() && hci.isOpen() && !adapterInfo->isCurrentSettingBitSet(AdapterSetting::POWERED);
}
/**
* Returns whether the adapter is valid, i.e. reference is valid, plugged in and generally operational,
* but not necessarily DBTAdapter::isPowered() powered.
* @return true if this adapter references are valid and hasn't been DBTAdapter::close() 'ed
* @see #isPowered()
* @see #isSuspended()
*/
bool isValid() const noexcept {
return DBTObject::isValid();
}
EUI48 const & getAddress() const noexcept { return adapterInfo->address; }
std::string getAddressString() const noexcept { return adapterInfo->address.toString(); }
/**
* Returns the system name.
*/
std::string getName() const noexcept { return adapterInfo->getName(); }
/**
* Returns the short system name.
*/
std::string getShortName() const noexcept { return adapterInfo->getShortName(); }
/**
* Returns the local friendly name and short_name. Contains empty strings if not set.
*
* The value is being updated via SET_LOCAL_NAME management event reply.
*
*/
const NameAndShortName & getLocalName() const noexcept { return localName; }
/**
* Sets the local friendly name.
*
* Returns the immediate SET_LOCAL_NAME reply if successful, otherwise nullptr.
* The corresponding management event will be received separately.
*
*/
std::shared_ptr setLocalName(const std::string &name, const std::string &short_name) noexcept;
/**
* Set the discoverable state of the adapter.
*/
bool setDiscoverable(bool value) noexcept;
/**
* Set the bondable (aka pairable) state of the adapter.
*/
bool setBondable(bool value) noexcept;
/**
* Set the power state of the adapter.
*/
bool setPowered(bool value) noexcept;
/**
* Reset the adapter.
*
* The semantics are specific to the HCI host implementation,
* however, it shall comply at least with the HCI Reset command
* and bring up the device from standby into a POWERED functional state afterwards.
*
*
* BT Core Spec v5.2: Vol 4, Part E HCI: 7.3.2 Reset command
*
*/
HCIStatusCode reset() noexcept;
/**
* Returns a reference to the used singleton DBTManager instance, used to create this adapter.
*/
DBTManager& getManager() const noexcept { return mgmt; }
/**
* Returns a reference to the aggregated HCIHandler instance.
*/
HCIHandler& getHCI() noexcept { return hci; }
/**
* Returns true, if the adapter's device is already whitelisted.
*/
bool isDeviceWhitelisted(const EUI48 &address) noexcept;
/**
* Add the given device to the adapter's autoconnect whitelist.
*
* The given connection parameter will be uploaded to the kernel for the given device first.
*
*
* Method will reject duplicate devices, in which case it should be removed first.
*
*
* @param address
* @param address_type
* @param ctype
* @param conn_interval_min in units of 1.25ms, default value 12 for 15ms; Value range [6 .. 3200] for [7.5ms .. 4000ms]
* @param conn_interval_max in units of 1.25ms, default value 12 for 15ms; Value range [6 .. 3200] for [7.5ms .. 4000ms]
* @param conn_latency slave latency in units of connection events, default value 0; Value range [0 .. 0x01F3].
* @param supervision_timeout in units of 10ms, default value >= 10 x conn_interval_max, we use HCIConstInt::LE_CONN_MIN_TIMEOUT_MS minimum; Value range [0xA-0x0C80] for [100ms - 32s].
* @return true if the device was already added or has been newly added to the adapter's whitelist.
*/
bool addDeviceToWhitelist(const EUI48 &address, const BDAddressType address_type,
const HCIWhitelistConnectType ctype,
const uint16_t conn_interval_min=12, const uint16_t conn_interval_max=12,
const uint16_t conn_latency=0, const uint16_t supervision_timeout=getHCIConnSupervisorTimeout(0, 15));
/** Remove the given device from the adapter's autoconnect whitelist. */
bool removeDeviceFromWhitelist(const EUI48 &address, const BDAddressType address_type);
// device discovery aka device scanning
/**
* Add the given listener to the list if not already present.
*
* Returns true if the given listener is not element of the list and has been newly added,
* otherwise false.
*
*
* The newly added AdapterStatusListener will receive an initial
* AdapterStatusListener::adapterSettingsChanged(..) event,
* passing an empty AdapterSetting::NONE oldMask and current AdapterSetting newMask.
* This allows the receiver to be aware of this adapter's current settings.
*
*/
bool addStatusListener(std::shared_ptr l);
/**
* Remove the given listener from the list.
*
* Returns true if the given listener is an element of the list and has been removed,
* otherwise false.
*
*/
bool removeStatusListener(std::shared_ptr l);
/**
* Remove the given listener from the list.
*
* Returns true if the given listener is an element of the list and has been removed,
* otherwise false.
*
*/
bool removeStatusListener(const AdapterStatusListener * l);
/**
* Remove all status listener from the list.
*
* Returns the number of removed event listener.
*
*/
int removeAllStatusListener();
/**
* Starts a new discovery session.
*
* Returns HCIStatusCode::SUCCESS if successful, otherwise the HCIStatusCode error state;
*
*
* if {@code keepAlive} is {@code true}, discovery state will be re-enabled
* in case the underlying Bluetooth implementation (BlueZ, ..) disabled it.
* Default is {@code true}.
*
*
* Using startDiscovery(keepAlive=true) and stopDiscovery()
* is the recommended workflow for a reliable discovery process.
*
*
* + --+-------+--------+-----------+----------------------------------------------------+
* | # | 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
* +---+-------+--------+-----------+----------------------------------------------------+
*
*
* Remaining default parameter values are chosen for using public address resolution
* and usual discovery intervals etc.
*
*
* This adapter's HCIHandler instance is used to initiate scanning,
* see HCIHandler::le_set_scan_param() and HCIHandler::le_enable_scan().
*
*
* Method will always clear previous discovered devices via removeDiscoveredDevices().
*
* @param keepAlive
* @param own_mac_type
* @param le_scan_interval in units of 0.625ms, default value 24 for 15ms; Value range [4 .. 0x4000] for [2.5ms .. 10.24s]
* @param le_scan_window in units of 0.625ms, default value 24 for 15ms; Value range [4 .. 0x4000] for [2.5ms .. 10.24s]. Shall be <= le_scan_interval
* @return HCIStatusCode::SUCCESS if successful, otherwise the HCIStatusCode error state
*/
HCIStatusCode startDiscovery(const bool keepAlive=true, const HCILEOwnAddressType own_mac_type=HCILEOwnAddressType::PUBLIC,
const uint16_t le_scan_interval=24, const uint16_t le_scan_window=24);
/**
* Closes the discovery session.
*
* This adapter's HCIHandler instance is used to stop scanning,
* see HCIHandler::le_enable_scan().
*
* @return HCIStatusCode::SUCCESS if successful, otherwise the HCIStatusCode error state
*/
HCIStatusCode stopDiscovery() noexcept;
/**
* Returns the current meta discovering ScanType. It can be modified through startDiscovery(..) and stopDiscovery().
*
* Note that if startDiscovery(..) has been issued with keepAlive==true,
* the meta ScanType will still keep the desired ScanType enabled
* even if it has been temporarily disabled.
*
* @see startDiscovery()
* @see stopDiscovery()
*/
ScanType getCurrentScanType() const noexcept {
return currentMetaScanType;
}
/**
* Returns the adapter's current native discovering ScanType. It can be modified through startDiscovery(..) and stopDiscovery().
* @see startDiscovery()
* @see stopDiscovery()
*/
ScanType getCurrentNativeScanType() const noexcept{
return hci.getCurrentScanType();
}
/**
* Returns the meta discovering state. It can be modified through startDiscovery(..) and stopDiscovery().
* @see startDiscovery()
* @see stopDiscovery()
*/
bool getDiscovering() const noexcept {
return ScanType::NONE != currentMetaScanType;
}
/**
* Returns discovered devices from the last discovery.
*
* Note that this list will be cleared when a new discovery is started over via startDiscovery().
*
*
* Note that devices in this list might be no more available,
* use 'DeviceStatusListener::deviceFound(..)' callback.
*
*/
std::vector> getDiscoveredDevices() const noexcept;
/** Discards all discovered devices. Returns number of removed discovered devices. */
int removeDiscoveredDevices() noexcept;
/** Returns shared DBTDevice if found, otherwise nullptr */
std::shared_ptr findDiscoveredDevice (EUI48 const & mac, const BDAddressType macType) noexcept;
std::string toString() const noexcept override { return toString(true); }
std::string toString(bool includeDiscoveredDevices) const noexcept;
/**
* This is a debug facility only, to observe consistency
* of the internally maintained lists of shared_ptr.
*/
void printSharedPtrListOfDevices() noexcept;
};
} // namespace direct_bt
#endif /* DBT_ADAPTER_HPP_ */