diff options
author | Sven Gothel <[email protected]> | 2020-04-09 01:02:40 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2020-04-09 01:02:40 +0200 |
commit | 8891be2fd6ecb4386ee7bdbb61ccc209b54f5bcf (patch) | |
tree | 0d54edb80e50c8389ce340e3e66dd35d0f5fa4b3 /src/direct_bt | |
parent | db63cc6278beed8a7fe2899af9f31995796a9651 (diff) |
Implement direct_bt: Direct Bluetooth access via Linux's Kernel BlueZ protocol stack w/o D-Bus bluetoothd.
By dropping BlueZ userspace D-Bus bluetoothd, we target high-performance reliable bluetooth support
with least dependencies for embedded device configurations.
See COPYING, describing which Linux Kernel BlueZ IOCTL information has been included in the source tree.
We claim Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl):
<https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note>
and hence maintain this project's license.
The new direct_bt feature set is organized as follows
- include/cppunit
- api/direct_bt
- api/ieee11073
- src/direct_bt
- examples/direct_bt
Note that the C++ direct_bt layer is not backward compatible to tinyb.
Since the Java layer still needs to be completed,
it has to be seen whether we will achieve compatibility
or drop the original D-Bus tinyb module altogether
in favor of a more streamlined API and implementation.
Current state allows scanning for LE devices, connecting
and parsing GATT plus receiving notifications and indications.
See examples/direct_bt_scanner/dbt_scanner.cpp.
The Linux Kernel BlueZ is configured via module MgmtComm.[hpp/cpp]
Diffstat (limited to 'src/direct_bt')
-rw-r--r-- | src/direct_bt/ATTPDUTypes.cpp | 156 | ||||
-rw-r--r-- | src/direct_bt/BTTypes.cpp | 350 | ||||
-rw-r--r-- | src/direct_bt/BasicTypes.cpp | 321 | ||||
-rw-r--r-- | src/direct_bt/CMakeLists.txt | 52 | ||||
-rw-r--r-- | src/direct_bt/GATTHandler.cpp | 866 | ||||
-rw-r--r-- | src/direct_bt/GATTNumbers.cpp | 507 | ||||
-rw-r--r-- | src/direct_bt/GATTTypes.cpp | 114 | ||||
-rw-r--r-- | src/direct_bt/HCIAdapter.cpp | 391 | ||||
-rw-r--r-- | src/direct_bt/HCIComm.cpp | 550 | ||||
-rw-r--r-- | src/direct_bt/HCIDevice.cpp | 226 | ||||
-rw-r--r-- | src/direct_bt/L2CAPComm.cpp | 244 | ||||
-rw-r--r-- | src/direct_bt/MgmtComm.cpp | 554 | ||||
-rw-r--r-- | src/direct_bt/UUID.cpp | 199 | ||||
-rw-r--r-- | src/direct_bt/dbt_debug.hpp | 63 | ||||
-rw-r--r-- | src/direct_bt/direct_bt.pc.cmake | 11 |
15 files changed, 4604 insertions, 0 deletions
diff --git a/src/direct_bt/ATTPDUTypes.cpp b/src/direct_bt/ATTPDUTypes.cpp new file mode 100644 index 00000000..64ab4a40 --- /dev/null +++ b/src/direct_bt/ATTPDUTypes.cpp @@ -0,0 +1,156 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "ATTPDUTypes.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +#define OPCODE_ENUM(X) \ + X(ATT_PDU_UNDEFINED) \ + X(ATT_ERROR_RSP) \ + X(ATT_EXCHANGE_MTU_REQ) \ + X(ATT_EXCHANGE_MTU_RSP) \ + X(ATT_FIND_INFORMATION_REQ) \ + X(ATT_FIND_INFORMATION_RSP) \ + X(ATT_FIND_BY_TYPE_VALUE_REQ) \ + X(ATT_FIND_BY_TYPE_VALUE_RSP) \ + X(ATT_READ_BY_TYPE_REQ) \ + X(ATT_READ_BY_TYPE_RSP) \ + X(ATT_READ_REQ) \ + X(ATT_READ_RSP) \ + X(ATT_READ_BLOB_REQ) \ + X(ATT_READ_BLOB_RSP) \ + X(ATT_READ_MULTIPLE_REQ) \ + X(ATT_READ_MULTIPLE_RSP) \ + X(ATT_READ_BY_GROUP_TYPE_REQ) \ + X(ATT_READ_BY_GROUP_TYPE_RSP) \ + X(ATT_WRITE_REQ) \ + X(ATT_WRITE_RSP) \ + X(ATT_WRITE_CMD) \ + X(ATT_PREPARE_WRITE_REQ) \ + X(ATT_PREPARE_WRITE_RSP) \ + X(ATT_EXECUTE_WRITE_REQ) \ + X(ATT_EXECUTE_WRITE_RSP) \ + X(ATT_READ_MULTIPLE_VARIABLE_REQ) \ + X(ATT_READ_MULTIPLE_VARIABLE_RSP) \ + X(ATT_MULTIPLE_HANDLE_VALUE_NTF) \ + X(ATT_HANDLE_VALUE_NTF) \ + X(ATT_HANDLE_VALUE_IND) \ + X(ATT_HANDLE_VALUE_CFM) \ + X(ATT_SIGNED_WRITE_CMD) + +#define CASE_TO_STRING(V) case V: return #V; + +std::string AttPDUMsg::getOpcodeString(const Opcode opc) { + switch(opc) { + OPCODE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown Opcode"; +} + +std::string AttErrorRsp::getPlainErrorString(const ErrorCode errorCode) { + switch(errorCode) { + case INVALID_HANDLE: return "Invalid Handle"; + case NO_READ_PERM: return "Read Not Permitted"; + case NO_WRITE_PERM: return "Write Not Permitted"; + case INVALID_PDU: return "Invalid PDU"; + case INSUFF_AUTHENTICATION: return "Insufficient Authentication"; + case UNSUPPORTED_REQUEST: return "Request Not Supported"; + case INVALID_OFFSET: return "Invalid Offset"; + case INSUFF_AUTHORIZATION: return "Insufficient Authorization"; + case PREPARE_QUEUE_FULL: return "Prepare Queue Full"; + case ATTRIBUTE_NOT_FOUND: return "Attribute Not Found"; + case ATTRIBUTE_NOT_LONG: return "Attribute Not Long"; + case INSUFF_ENCRYPTION_KEY_SIZE: return "Insufficient Encryption Key Size"; + case INVALID_ATTRIBUTE_VALUE_LEN: return "Invalid Attribute Value Length"; + case UNLIKELY_ERROR: return "Unlikely Error"; + case INSUFF_ENCRYPTION: return "Insufficient Encryption"; + case UNSUPPORTED_GROUP_TYPE: return "Unsupported Group Type"; + case INSUFFICIENT_RESOURCES: return "Insufficient Resources"; + case DB_OUT_OF_SYNC: return "Database Out Of Sync"; + case FORBIDDEN_VALUE: return "Value Not Allowed"; + default: ; // fall through intended + } + if( 0x80 <= errorCode && errorCode <= 0x9F ) { + return "Application Error"; + } + if( 0xE0 <= errorCode && errorCode <= 0xFF ) { + return "Common Profile and Services Error"; + } + return "Error Reserved for future use"; +} + +AttPDUMsg* AttPDUMsg::getSpecialized(const uint8_t * buffer, int const buffer_size) { + const uint8_t opc = *buffer; + AttPDUMsg * res; + switch( opc ) { + case ATT_PDU_UNDEFINED: res = new AttPDUUndefined(buffer, buffer_size); break; + case ATT_ERROR_RSP: res = new AttErrorRsp(buffer, buffer_size); break; + case ATT_EXCHANGE_MTU_REQ: res = new AttExchangeMTU(buffer, buffer_size); break; + case ATT_EXCHANGE_MTU_RSP: res = new AttExchangeMTU(buffer, buffer_size); break; + case ATT_FIND_INFORMATION_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_FIND_INFORMATION_RSP: res = new AttFindInfoRsp(buffer, buffer_size); break; + case ATT_FIND_BY_TYPE_VALUE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_FIND_BY_TYPE_VALUE_RSP: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_BY_TYPE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_BY_TYPE_RSP: res = new AttReadByTypeRsp(buffer, buffer_size); break; + case ATT_READ_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_RSP: res = new AttReadRsp(buffer, buffer_size); break; + case ATT_READ_BLOB_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_BLOB_RSP: res = new AttReadBlobRsp(buffer, buffer_size); break; + case ATT_READ_MULTIPLE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_MULTIPLE_RSP: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_BY_GROUP_TYPE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_BY_GROUP_TYPE_RSP: res = new AttReadByGroupTypeRsp(buffer, buffer_size); break; + case ATT_WRITE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_WRITE_RSP: res = new AttWriteRsp(buffer, buffer_size); break; + case ATT_WRITE_CMD: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_PREPARE_WRITE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_PREPARE_WRITE_RSP: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_EXECUTE_WRITE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_EXECUTE_WRITE_RSP: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_MULTIPLE_VARIABLE_REQ: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_READ_MULTIPLE_VARIABLE_RSP: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_MULTIPLE_HANDLE_VALUE_NTF: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_HANDLE_VALUE_NTF: res = new AttHandleValueRcv(buffer, buffer_size); break; + case ATT_HANDLE_VALUE_IND: res = new AttHandleValueRcv(buffer, buffer_size); break; + case ATT_HANDLE_VALUE_CFM: res = new AttPDUMsg(buffer, buffer_size); break; + case ATT_SIGNED_WRITE_CMD: res = new AttPDUMsg(buffer, buffer_size); break; + default: res = new AttPDUMsg(buffer, buffer_size); break; + } + return res; +} diff --git a/src/direct_bt/BTTypes.cpp b/src/direct_bt/BTTypes.cpp new file mode 100644 index 00000000..bfd955f6 --- /dev/null +++ b/src/direct_bt/BTTypes.cpp @@ -0,0 +1,350 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "BTTypes.hpp" + +#include "dbt_debug.hpp" + +std::string EUI48::toString() const { + const int length = 17; + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + b[5], b[4], b[3], b[2], b[1], b[0]); + if( length != count ) { + throw direct_bt::InternalError("EUI48 string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +EUI48::EUI48(const std::string str) { + if( 17 != str.length() ) { + std::string msg("EUI48 string not of length 17 but "); + msg.append(std::to_string(str.length())); + msg.append(": "+str); + throw direct_bt::IllegalArgumentException(msg, E_FILE_LINE); + } + if ( sscanf(str.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &b[5], &b[4], &b[3], &b[2], &b[1], &b[0]) != 6 ) + { + std::string msg("EUI48 string not in format '00:00:00:00:00:00' but "+str); + throw direct_bt::IllegalArgumentException(msg, E_FILE_LINE); + } + + // sscanf provided host data type, in which we store the values, + // hence no endian conversion +} + +EUI48::EUI48(const uint8_t * _b) { + memcpy(b, _b, sizeof(b)); +} + +const EUI48 EUI48_ANY_DEVICE; // default ctor is zero bytes! +static uint8_t _EUI48_ALL_DEVICE[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +static uint8_t _EUI48_LOCAL_DEVICE[] = {0x00, 0x00, 0x00, 0xff, 0xff, 0xff}; +const EUI48 EUI48_ALL_DEVICE( _EUI48_ALL_DEVICE ); +const EUI48 EUI48_LOCAL_DEVICE( _EUI48_LOCAL_DEVICE ); + +// ************************************************* +// ************************************************* +// ************************************************* + +using namespace direct_bt; + +static inline const int8_t * const_uint8_to_const_int8_ptr(const uint8_t* p) { + return static_cast<const int8_t *>( static_cast<void *>( const_cast<uint8_t*>( p ) ) ); +} + +static std::string bt_compidtostr(const uint16_t companyid) { + return std::to_string(companyid); +} + +ManufactureSpecificData::ManufactureSpecificData(uint16_t const company, uint8_t const * const data, int const data_len) +: company(company), companyName(std::string(bt_compidtostr(company))), data_len(data_len), data(new uint8_t[data_len]) { + memcpy(this->data.get(), data, data_len); +} + +std::string ManufactureSpecificData::toString() const { + std::string out("MSD["); + out.append(std::to_string(company)+" "+companyName); + out.append(", data "+std::to_string(data_len)+" bytes]"); + return out; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +std::string EInfoReport::getSourceString() const { + switch (source) { + case Source::NA: return "N/A"; + case Source::AD: return "AD"; + case Source::EIR: return "EIR"; + } + return "N/A"; +} + +void EInfoReport::setName(const uint8_t *buffer, int buffer_len) { + name = get_string(buffer, buffer_len, 30); + set(Element::NAME); +} + +void EInfoReport::setShortName(const uint8_t *buffer, int buffer_len) { + name_short = get_string(buffer, buffer_len, 30); + set(Element::NAME_SHORT); +} + +void EInfoReport::addService(std::shared_ptr<uuid_t> const &uuid) +{ + auto begin = services.begin(); + auto it = std::find_if(begin, services.end(), [&](std::shared_ptr<uuid_t> const& p) { + return *p == *uuid; + }); + if ( it == std::end(services) ) { + services.push_back(uuid); + } +} + +std::string EInfoReport::dataSetToString(const uint32_t data_set) { + std::string out("["); + if( isSet(data_set, Element::EVT_TYPE) ) { + out.append("EVT_TYPE, "); + } + if( isSet(data_set, Element::BDADDR) ) { + out.append("BDADDR, "); + } + if( isSet(data_set, Element::NAME) ) { + out.append("NAME, "); + } + if( isSet(data_set, Element::NAME_SHORT) ) { + out.append("NAME_SHORT, "); + } + if( isSet(data_set, Element::RSSI) ) { + out.append("RSSI, "); + } + if( isSet(data_set, Element::TX_POWER) ) { + out.append("TX_POWER, "); + } + if( isSet(data_set, Element::MANUF_DATA) ) { + out.append("MANUF_DATA, "); + } + out.append("]"); + return out; +} +std::string EInfoReport::dataSetToString() const { + return std::string("DataSet"+dataSetToString(data_set)); +} +std::string EInfoReport::toString() const { + std::string msdstr = nullptr != msd ? msd->toString() : "MSD[null]"; + std::string out("EInfoReport::"+getSourceString()+"["+getAddressString()+", "+name+"/"+name_short+", "+dataSetToString()+ + ", evt-type "+std::to_string(evt_type)+", rssi "+std::to_string(rssi)+ + ", tx-power "+std::to_string(tx_power)+", "+msdstr+"]"); + if(services.size() > 0 ) { + out.append("\n"); + for(auto it = services.begin(); it != services.end(); it++) { + std::shared_ptr<uuid_t> p = *it; + out.append(" ").append(p->toUUID128String()).append(", ").append(std::to_string(static_cast<int>(p->getTypeSize()))).append(" bytes\n"); + } + } + return out; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +int EInfoReport::next_data_elem(uint8_t *eir_elem_len, uint8_t *eir_elem_type, uint8_t const **eir_elem_data, + uint8_t const * data, int offset, int const size) +{ + if (offset < size) { + uint8_t len = data[offset]; // covers: type + data, less len field itself + + if (len == 0) { + return 0; // end of significant part + } + + if (len + offset > size) { + return -ENOENT; + } + + *eir_elem_type = data[offset + 1]; + *eir_elem_data = data + offset + 2; // net data ptr + *eir_elem_len = len - 1; // less type -> net data length + + return offset + 1 + len; // next ad_struct offset: + len + type + data + } + return -ENOENT; +} + +int EInfoReport::read_data(uint8_t const * data, uint8_t const data_length) { + int count = 0; + int offset = 0; + uint8_t elem_len, elem_type; + uint8_t const *elem_data; + + while( 0 < ( offset = next_data_elem( &elem_len, &elem_type, &elem_data, data, offset, data_length ) ) ) + { + DBG_PRINT("%s-Element[%d] @ [%d/%d]: type 0x%.2X with %d bytes net", + getSourceString().c_str(), count, offset, data_length, elem_type, elem_len); + count++; + + // Guaranteed: eir_elem_len >= 0! + switch ( elem_type ) { + case GAP_T::FLAGS: + // FIXME + break; + case GAP_T::UUID16_INCOMPLETE: + case GAP_T::UUID16_COMPLETE: + for(int j=0; j<elem_len/2; j++) { + const std::shared_ptr<uuid_t> uuid(new uuid16_t(elem_data, j*2, true)); + addService(std::move(uuid)); + } + break; + case GAP_T::UUID32_INCOMPLETE: + case GAP_T::UUID32_COMPLETE: + for(int j=0; j<elem_len/4; j++) { + const std::shared_ptr<uuid_t> uuid(new uuid32_t(elem_data, j*4, true)); + addService(std::move(uuid)); + } + break; + case GAP_T::UUID128_INCOMPLETE: + case GAP_T::UUID128_COMPLETE: + for(int j=0; j<elem_len/16; j++) { + const std::shared_ptr<uuid_t> uuid(new uuid128_t(elem_data, j*16, true)); + addService(std::move(uuid)); + } + break; + case GAP_T::NAME_LOCAL_SHORT: + case GAP_T::NAME_LOCAL_COMPLETE: { + if( GAP_T::NAME_LOCAL_COMPLETE == elem_type ) { + setName(elem_data, elem_len); + } else { + setShortName(elem_data, elem_len); + } + } break; + case GAP_T::TX_POWER_LEVEL: + setTxPower(*const_uint8_to_const_int8_ptr(elem_data)); + break; + + case GAP_T::SSP_CLASS_OF_DEVICE: + case GAP_T::DEVICE_ID: + case GAP_T::SOLICIT_UUID16: + case GAP_T::SOLICIT_UUID128: + case GAP_T::SVC_DATA_UUID16: + case GAP_T::PUB_TRGT_ADDR: + case GAP_T::RND_TRGT_ADDR: + case GAP_T::GAP_APPEARANCE: + case GAP_T::SOLICIT_UUID32: + case GAP_T::SVC_DATA_UUID32: + case GAP_T::SVC_DATA_UUID128: + break; + + case GAP_T::MANUFACTURE_SPECIFIC: { + uint16_t company = get_uint16(elem_data, 0, true /* littleEndian */); + setManufactureSpecificData(company, elem_data+2, elem_len-2); + } break; + + default: { + // FIXME: Use a data blob!!!! + fprintf(stderr, "%s-Element @ [%d/%d]: Warning: Unhandled type 0x%.2X with %d bytes net\n", + getSourceString().c_str(), offset, data_length, elem_type, elem_len); + } break; + } + } + return count; +} + +std::vector<std::shared_ptr<EInfoReport>> EInfoReport::read_ad_reports(uint8_t const * data, uint8_t const data_length) { + int const num_reports = (int) data[0]; + std::vector<std::shared_ptr<EInfoReport>> ad_reports; + + if( 0 >= num_reports || num_reports > 0x19 ) { + DBG_PRINT("AD-Reports: Invalid reports count: %d", num_reports); + return ad_reports; + } + uint8_t const *limes = data + data_length; + uint8_t const *i_octets = data + 1; + uint8_t ad_data_len[0x19]; + const int segment_count = 6; + int read_segments = 0; + int i; + + for(i = 0; i < num_reports && i_octets < limes; i++) { + ad_reports.push_back(std::shared_ptr<EInfoReport>(new EInfoReport())); + ad_reports[i]->setSource(Source::AD); + ad_reports[i]->setTimestamp(getCurrentMilliseconds()); + ad_reports[i]->setEvtType(*i_octets++); + read_segments++; + } + for(i = 0; i < num_reports && i_octets < limes; i++) { + ad_reports[i]->setAddressType(*i_octets++); + read_segments++; + } + for(i = 0; i < num_reports && i_octets + 5 < limes; i++) { + ad_reports[i]->setAddress( *((EUI48 const *)i_octets) ); + i_octets += 6; + read_segments++; + } + for(i = 0; i < num_reports && i_octets < limes; i++) { + ad_data_len[i] = *i_octets++; + read_segments++; + } + for(i = 0; i < num_reports && i_octets + ad_data_len[i] < limes; i++) { + ad_reports[i]->read_data(i_octets, ad_data_len[i]); + i_octets += ad_data_len[i]; + read_segments++; + } + for(i = 0; i < num_reports && i_octets < limes; i++) { + ad_reports[i]->setRSSI(*const_uint8_to_const_int8_ptr(i_octets)); + i_octets++; + read_segments++; + } + const int bytes_left = limes - i_octets; + + if( segment_count != read_segments ) { + fprintf(stderr, "AD-Reports: Warning: Incomplete %d reports within %d bytes: Segment read %d < %d, data-ptr %d bytes to limes\n", + num_reports, data_length, read_segments, segment_count, bytes_left); + } else { + DBG_PRINT("AD-Reports: Completed %d reports within %d bytes: Segment read %d == %d, data-ptr %d bytes to limes", + num_reports, data_length, read_segments, segment_count, bytes_left); + } + return ad_reports; +} + +// ************************************************* +// ************************************************* +// ************************************************* + diff --git a/src/direct_bt/BasicTypes.cpp b/src/direct_bt/BasicTypes.cpp new file mode 100644 index 00000000..aa848b7e --- /dev/null +++ b/src/direct_bt/BasicTypes.cpp @@ -0,0 +1,321 @@ +/* + * 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 "BasicTypes.hpp" + +#include <cstdint> +#include <cinttypes> + +#include <algorithm> + +// #define _USE_BACKTRACE_ 1 + +#if _USE_BACKTRACE_ +extern "C" { + #include <execinfo.h> +} +#endif + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +static const int64_t NanoPerMilli = 1000000L; +static const int64_t MilliPerOne = 1000L; + +/** + * See <http://man7.org/linux/man-pages/man2/clock_gettime.2.html> + * <p> + * Regarding avoiding kernel via VDSO, + * see <http://man7.org/linux/man-pages/man7/vdso.7.html>, + * clock_gettime seems to be well supported at least on kernel >= 4.4. + * Only bfin and sh are missing, while ia64 seems to be complicated. + */ +int64_t direct_bt::getCurrentMilliseconds() { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_sec * MilliPerOne + t.tv_nsec / NanoPerMilli; +} + +const char* RuntimeException::what() const noexcept { +#if _USE_BACKTRACE_ + std::string out(msg); + void *buffers[10]; + size_t nptrs = backtrace(buffers, 10); + char **symbols = backtrace_symbols(buffers, nptrs); + if( NULL != symbols ) { + out.append("\nBacktrace:\n"); + for(int i=0; i<nptrs; i++) { + out.append(symbols[i]).append("\n"); + } + free(symbols); + } + return out.c_str(); +#else + return msg.c_str(); +#endif +} + +std::string direct_bt::get_string(const uint8_t *buffer, int const buffer_len, int const max_len) { + const int cstr_len = std::min(buffer_len, max_len); + char cstr[max_len+1]; // EOS + memcpy(cstr, buffer, cstr_len); + cstr[cstr_len] = 0; // EOS + return std::string(cstr); +} + +uint128_t direct_bt::merge_uint128(uint16_t const uuid16, uint128_t const & base_uuid, int const uuid16_le_octet_index) +{ + if( 0 > uuid16_le_octet_index || uuid16_le_octet_index > 14 ) { + std::string msg("uuid16_le_octet_index "); + msg.append(std::to_string(uuid16_le_octet_index)); + msg.append(", not within [0..14]"); + throw IllegalArgumentException(msg, E_FILE_LINE); + } + uint128_t dest = base_uuid; + + // base_uuid: 00000000-0000-1000-8000-00805F9B34FB + // uuid16: DCBA + // uuid16_le_octet_index: 12 + // result: 0000DCBA-0000-1000-8000-00805F9B34FB + // + // LE: low-mem - FB349B5F8000-0080-0010-0000-ABCD0000 - high-mem + // ^ index 12 + // LE: uuid16 -> value.data[12+13] + // + // BE: low-mem - 0000DCBA-0000-1000-8000-00805F9B34FB - high-mem + // ^ index 2 + // BE: uuid16 -> value.data[2+3] + // +#if __BYTE_ORDER == __BIG_ENDIAN + int offset = 15 - 1 - uuid16_le_octet_index; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + int offset = uuid16_le_octet_index; +#else +#error "Unexpected __BYTE_ORDER" +#endif + uint16_t * destu16 = (uint16_t*)(dest.data + offset); + *destu16 += uuid16; + return dest; +} + +uint128_t direct_bt::merge_uint128(uint32_t const uuid32, uint128_t const & base_uuid, int const uuid32_le_octet_index) +{ + if( 0 > uuid32_le_octet_index || uuid32_le_octet_index > 12 ) { + std::string msg("uuid32_le_octet_index "); + msg.append(std::to_string(uuid32_le_octet_index)); + msg.append(", not within [0..12]"); + throw IllegalArgumentException(msg, E_FILE_LINE); + } + uint128_t dest = base_uuid; + + // base_uuid: 00000000-0000-1000-8000-00805F9B34FB + // uuid32: 87654321 + // uuid32_le_octet_index: 12 + // result: 87654321-0000-1000-8000-00805F9B34FB + // + // LE: low-mem - FB349B5F8000-0080-0010-0000-12345678 - high-mem + // ^ index 12 + // LE: uuid32 -> value.data[12..15] + // + // BE: low-mem - 87654321-0000-1000-8000-00805F9B34FB - high-mem + // ^ index 0 + // BE: uuid32 -> value.data[0..3] + // +#if __BYTE_ORDER == __BIG_ENDIAN + int offset = 15 - 3 - uuid32_le_octet_index; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + int offset = uuid32_le_octet_index; +#else +#error "Unexpected __BYTE_ORDER" +#endif + uint32_t * destu32 = (uint32_t*)(dest.data + offset); + *destu32 += uuid32; + return dest; +} + +std::string direct_bt::uint8HexString(const uint8_t v, const bool leading0X) { + const int length = leading0X ? 4 : 2; // ( '0x00' | '00' ) + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), ( leading0X ? "0x%.2X" : "%.2X" ), v); + if( length != count ) { + throw InternalError("uint8_t string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +std::string direct_bt::uint16HexString(const uint16_t v, const bool leading0X) { + const int length = leading0X ? 6 : 4; // ( '0x0000' | '0000' ) + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), ( leading0X ? "0x%.4X" : "%.4X" ), v); + if( length != count ) { + throw InternalError("uint16_t string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +std::string direct_bt::uint32HexString(const uint32_t v, const bool leading0X) { + const int length = leading0X ? 10 : 8; // ( '0x00000000' | '00000000' ) + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), ( leading0X ? "0x%.8X" : "%.8X" ), v); + if( length != count ) { + throw InternalError("uint32_t string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +static const char* HEX_ARRAY = "0123456789ABCDEF"; + +std::string direct_bt::bytesHexString(const uint8_t * bytes, const int offset, const int length, const bool lsbFirst, const bool leading0X) { + std::string str; + + if( nullptr == bytes ) { + return "null"; + } + if( 0 == length ) { + return "nil"; + } + if( leading0X ) { + str.reserve(2 + length * 2 +1); + str.push_back('0'); + str.push_back('x'); + } else { + str.reserve(length * 2 +1); + } + if( lsbFirst ) { + // LSB left -> MSB right + for (int j = 0; j < length; j++) { + const int v = bytes[offset+j] & 0xFF; + str.push_back(HEX_ARRAY[v >> 4]); + str.push_back(HEX_ARRAY[v & 0x0F]); + } + } else { + // MSB left -> LSB right + for (int j = length-1; j >= 0; j--) { + const int v = bytes[offset+j] & 0xFF; + str.push_back(HEX_ARRAY[v >> 4]); + str.push_back(HEX_ARRAY[v & 0x0F]); + } + } + return str; +} + +std::string direct_bt::int32SeparatedString(const int32_t v, const char separator) { + // INT32_MIN: -2147483648 int32_t 11 chars + // INT32_MIN: -2,147,483,648 int32_t 14 chars + // INT32_MAX: 2147483647 int32_t 10 chars + // INT32_MAX: 2,147,483,647 int32_t 13 chars + char src[16]; // aligned 4 byte + char dst[16]; // aligned 4 byte, also +1 for erroneous trailing comma + char *p_src = src; + char *p_dst = dst; + + int num_len = snprintf(src, sizeof(src), "%" PRId32, v); + + if (*p_src == '-') { + *p_dst++ = *p_src++; + num_len--; + } + + for ( int commas = 2 - num_len % 3; 0 != *p_src; commas = (commas + 1) % 3 ) + { + *p_dst++ = *p_src++; + if ( 1 == commas ) { + *p_dst++ = separator; + } + } + *--p_dst = 0; // place EOS on erroneous trailing comma + + return std::string(dst, p_dst - dst); +} + +std::string direct_bt::uint32SeparatedString(const uint32_t v, const char separator) { + // UINT32_MAX: 4294967295 uint32_t 10 chars + // UINT32_MAX: 4,294,967,295 uint32_t 13 chars + char src[16]; // aligned 4 byte + char dst[16]; // aligned 4 byte, also +1 for erroneous trailing comma + char *p_src = src; + char *p_dst = dst; + + int num_len = snprintf(src, sizeof(src), "%" PRIu32, v); + + for ( int commas = 2 - num_len % 3; 0 != *p_src; commas = (commas + 1) % 3 ) + { + *p_dst++ = *p_src++; + if ( 1 == commas ) { + *p_dst++ = separator; + } + } + *--p_dst = 0; // place EOS on erroneous trailing comma + + return std::string(dst, p_dst - dst); +} + +std::string direct_bt::uint64SeparatedString(const uint64_t v, const char separator) { + // UINT64_MAX: 18446744073709551615 uint64_t 20 chars + // UINT64_MAX: 18,446,744,073,709,551,615 uint64_t 26 chars + char src[28]; // aligned 4 byte + char dst[28]; // aligned 4 byte, also +1 for erroneous trailing comma + char *p_src = src; + char *p_dst = dst; + + int num_len = snprintf(src, sizeof(src), "%" PRIu64, v); + + for ( int commas = 2 - num_len % 3; 0 != *p_src; commas = (commas + 1) % 3 ) + { + *p_dst++ = *p_src++; + if ( 1 == commas ) { + *p_dst++ = separator; + } + } + *--p_dst = 0; // place EOS on erroneous trailing comma + + return std::string(dst, p_dst - dst); +} + +void direct_bt::trimInPlace(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +std::string direct_bt::trimCopy(const std::string &_s) { + std::string s(_s); + trimInPlace(s); + return s; +} + diff --git a/src/direct_bt/CMakeLists.txt b/src/direct_bt/CMakeLists.txt new file mode 100644 index 00000000..e04d7e41 --- /dev/null +++ b/src/direct_bt/CMakeLists.txt @@ -0,0 +1,52 @@ +set (direct_bt_LIB_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR}/api + ${PROJECT_SOURCE_DIR}/api/direct_bt + ${PROJECT_SOURCE_DIR}/include +) + +include_directories( + ${direct_bt_LIB_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} +) + +set (direct_bt_LIB_SRCS + ${PROJECT_SOURCE_DIR}/src/ieee11073/DataTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/BasicTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/UUID.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/BTTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/HCIComm.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/L2CAPComm.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/MgmtComm.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/HCIAdapter.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/HCIDevice.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/ATTPDUTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/GATTNumbers.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/GATTTypes.cpp + ${PROJECT_SOURCE_DIR}/src/direct_bt/GATTHandler.cpp +# autogenerated files + ${CMAKE_CURRENT_BINARY_DIR}/../version.c +) + +add_library (direct_bt SHARED ${direct_bt_LIB_SRCS}) +target_link_libraries ( + direct_bt + ${CMAKE_THREAD_LIBS_INIT} +) + +set_target_properties( + direct_bt + PROPERTIES + SOVERSION ${tinyb_VERSION_MAJOR} + VERSION ${tinyb_VERSION_STRING} + CXX_STANDARD 11 +) +install (DIRECTORY ${PROJECT_SOURCE_DIR}/api/direct_bt/ DESTINATION include/direct_bt) + +macro (tinyb_CREATE_INSTALL_PKGCONFIG generated_file install_location) + configure_file (${generated_file}.cmake ${CMAKE_CURRENT_BINARY_DIR}/${generated_file} @ONLY) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${generated_file} DESTINATION ${install_location}) +endmacro (tinyb_CREATE_INSTALL_PKGCONFIG) +tinyb_create_install_pkgconfig (direct_bt.pc lib${LIB_SUFFIX}/pkgconfig) + +install(TARGETS direct_bt LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + diff --git a/src/direct_bt/GATTHandler.cpp b/src/direct_bt/GATTHandler.cpp new file mode 100644 index 00000000..e0d75bee --- /dev/null +++ b/src/direct_bt/GATTHandler.cpp @@ -0,0 +1,866 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +extern "C" { + #include <unistd.h> + #include <sys/socket.h> + #include <poll.h> + #include <signal.h> +} + +// #define PERF_PRINT_ON 1 +// #define VERBOSE_ON 1 + +#include "L2CAPIoctl.hpp" +#include "GATTNumbers.hpp" + +#include "GATTHandler.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +#define STATE_ENUM(X) \ + X(Error) \ + X(Disconnected) \ + X(Connecting) \ + X(Connected) \ + X(RequestInProgress) \ + X(DiscoveringCharacteristics) \ + X(GetClientCharaceristicConfiguration) \ + X(WaitWriteResponse) \ + X(WaitReadResponse) + +#define CASE_TO_STRING(V) case V: return #V; + +std::string GATTHandler::getStateString(const State state) { + switch(state) { + STATE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown State"; +} + +GATTHandler::State GATTHandler::validateState() { + const bool a = Disconnected < state; + const bool b = l2cap->isOpen(); + const bool c = L2CAPComm::State::Disconnected < l2cap->getState(); + if( a || b || c ) { + // something is open ... + if( a != b || a != c || b != c ) { + throw InvalidStateException("Inconsistent open state: GattHandler "+getStateString()+ + ", l2cap[open "+std::to_string(b)+", state "+l2cap->getStateString()+"]", E_FILE_LINE); + } + } + return state; +} + +std::shared_ptr<GATTNotificationListener> GATTHandler::setGATTNotificationListener(std::shared_ptr<GATTNotificationListener> l) { + std::shared_ptr<GATTNotificationListener> o = gattNotificationListener; + gattNotificationListener = l; + return o; +} + +std::shared_ptr<GATTIndicationListener> GATTHandler::setGATTIndicationListener(std::shared_ptr<GATTIndicationListener> l, bool sendConfirmation) { + std::shared_ptr<GATTIndicationListener> o = gattIndicationListener; + gattIndicationListener = l; + sendIndicationConfirmation = sendConfirmation; + return o; +} + +void GATTHandler::l2capReaderThreadImpl() { + l2capReaderShallStop = false; + l2capReaderRunning = true; + INFO_PRINT("l2capReaderThreadImpl Started"); + + while( !l2capReaderShallStop ) { + int len; + if( Disconnected >= validateState() ) { + // not open + perror("GATTHandler::l2capReaderThread: Not connected"); + l2capReaderShallStop = true; + break; + } + + len = l2cap->read(rbuffer.get_wptr(), rbuffer.getSize(), Defaults::L2CAP_READER_THREAD_POLL_TIMEOUT); + if( 0 < len ) { + const AttPDUMsg * attPDU = AttPDUMsg::getSpecialized(rbuffer.get_ptr(), len); + const AttPDUMsg::Opcode opc = attPDU->getOpcode(); + + if( AttPDUMsg::Opcode::ATT_HANDLE_VALUE_NTF == opc ) { + const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU); + INFO_PRINT("GATTHandler: NTF: %s", a->toString().c_str()); + if( nullptr != gattNotificationListener ) { + GATTCharacterisicsDeclRef decl = findCharacterisics(a->getHandle()); + gattNotificationListener->notificationReceived(this->l2cap->getDevice(), decl, std::shared_ptr<const AttHandleValueRcv>(a)); + attPDU = nullptr; + } + } else if( AttPDUMsg::Opcode::ATT_HANDLE_VALUE_IND == opc ) { + const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU); + INFO_PRINT("GATTHandler: IND: %s, sendIndicationConfirmation %d", a->toString().c_str(), sendIndicationConfirmation); + bool cfmSent = false; + if( sendIndicationConfirmation ) { + AttHandleValueCfm cfm; + cfmSent = send(cfm); + DBG_PRINT("GATTHandler: CFM send: %s, confirmationSent %d", cfm.toString().c_str(), cfmSent); + } + if( nullptr != gattIndicationListener ) { + GATTCharacterisicsDeclRef decl = findCharacterisics(a->getHandle()); + gattIndicationListener->indicationReceived(this->l2cap->getDevice(), decl, std::shared_ptr<const AttHandleValueRcv>(a), cfmSent); + attPDU = nullptr; + } + } else if( AttPDUMsg::Opcode::ATT_MULTIPLE_HANDLE_VALUE_NTF == opc ) { + // FIXME TODO .. + INFO_PRINT("GATTHandler: MULTI-NTF: %s", attPDU->toString().c_str()); + } else { + attPDURing.putBlocking( std::shared_ptr<const AttPDUMsg>( attPDU ) ); + attPDU = nullptr; + } + if( nullptr != attPDU ) { + delete attPDU; // free unhandled PDU + } + } else if( ETIMEDOUT != errno && !l2capReaderShallStop ) { // expected exits + perror("GATTHandler::l2capReaderThread: l2cap read error"); + } + } + + INFO_PRINT("l2capReaderThreadImpl Ended"); + l2capReaderRunning = false; +} + +static void gatthandler_sigaction(int sig, siginfo_t *info, void *ucontext) { + INFO_PRINT("GATTHandler.sigaction: sig %d, info[code %d, errno %d, signo %d, pid %d, uid %d, fd %d]", + sig, info->si_code, info->si_errno, info->si_signo, + info->si_pid, info->si_uid, info->si_fd); + + if( SIGINT != sig ) { + return; + } + { + struct sigaction sa_setup; + bzero(&sa_setup, sizeof(sa_setup)); + sa_setup.sa_handler = SIG_DFL; + sigemptyset(&(sa_setup.sa_mask)); + sa_setup.sa_flags = 0; + if( 0 != sigaction( SIGINT, &sa_setup, NULL ) ) { + perror("GATTHandler.sigaction: Resetting sighandler"); + } + } +} + +bool GATTHandler::connect() { + if( Disconnected < validateState() ) { + // already open + DBG_PRINT("GATTHandler.connect: Already open"); + return true; + } + state = static_cast<GATTHandler::State>(l2cap->connect()); + + if( Disconnected >= validateState() ) { + DBG_PRINT("GATTHandler.connect: Could not connect"); + return false; + } + + { + struct sigaction sa_setup; + bzero(&sa_setup, sizeof(sa_setup)); + sa_setup.sa_sigaction = gatthandler_sigaction; + sigemptyset(&(sa_setup.sa_mask)); + sa_setup.sa_flags = SA_SIGINFO; + if( 0 != sigaction( SIGINT, &sa_setup, NULL ) ) { + perror("GATTHandler.connect: Setting sighandler"); + } + } + l2capReaderThread = std::thread(&GATTHandler::l2capReaderThreadImpl, this); + + const uint16_t mtu = exchangeMTU(ClientMaxMTU);; + if( 0 < mtu ) { + serverMTU = mtu; + } else { + WARN_PRINT("Ignoring zero serverMTU."); + } + usedMTU = std::min((int)ClientMaxMTU, (int)serverMTU); + + return true; +} + +GATTHandler::~GATTHandler() { + disconnect(); +} + +bool GATTHandler::disconnect() { + if( Disconnected >= validateState() ) { + // not open + return false; + } + DBG_PRINT("GATTHandler.disconnect Start"); + if( l2capReaderRunning && l2capReaderThread.joinable() ) { + l2capReaderShallStop = true; + pthread_t tid = l2capReaderThread.native_handle(); + pthread_kill(tid, SIGINT); + } + + l2cap->disconnect(); + state = Disconnected; + + if( l2capReaderRunning && l2capReaderThread.joinable() ) { + // still running .. + DBG_PRINT("GATTHandler.disconnect join l2capReaderThread"); + l2capReaderThread.join(); + } + l2capReaderThread = std::thread(); // empty + DBG_PRINT("GATTHandler.disconnect End"); + return Disconnected == validateState(); +} + +bool GATTHandler::send(const AttPDUMsg &msg) { + if( Disconnected >= validateState() ) { + // not open + return false; + } + if( msg.pdu.getSize() > usedMTU ) { + throw IllegalArgumentException("clientMaxMTU "+std::to_string(msg.pdu.getSize())+" > usedMTU "+std::to_string(usedMTU), E_FILE_LINE); + } + + const int res = l2cap->write(msg.pdu.get_ptr(), msg.pdu.getSize()); + if( 0 > res ) { + perror("GATTHandler::send: l2cap write error"); + state = Error; + return false; + } + return res == msg.pdu.getSize(); +} + +std::shared_ptr<const AttPDUMsg> GATTHandler::receiveNext() { + return attPDURing.getBlocking(); +} + +uint16_t GATTHandler::exchangeMTU(const uint16_t clientMaxMTU) { + /*** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.3.1 Exchange MTU (Server configuration) + */ + if( clientMaxMTU > ClientMaxMTU ) { + throw IllegalArgumentException("clientMaxMTU "+std::to_string(clientMaxMTU)+" > ClientMaxMTU "+std::to_string(ClientMaxMTU), E_FILE_LINE); + } + const AttExchangeMTU req(clientMaxMTU); + + PERF_TS_T0(); + + uint16_t mtu = 0; + DBG_PRINT("GATT send: %s", req.toString().c_str()); + + if( send(req) ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_EXCHANGE_MTU_RSP ) { + const AttExchangeMTU * p = static_cast<const AttExchangeMTU*>(pdu.get()); + mtu = p->getMTUSize(); + } + } + PERF_TS_TD("GATT exchangeMTU"); + + return mtu; +} + +GATTCharacterisicsDeclRef GATTHandler::findCharacterisics(const uint16_t charHandle) { + return findCharacterisics(charHandle, services); +} + +GATTCharacterisicsDeclRef GATTHandler::findCharacterisics(const uint16_t charHandle, std::vector<GATTPrimaryServiceRef> &services) { + for(auto it = services.begin(); it != services.end(); it++) { + GATTCharacterisicsDeclRef decl = findCharacterisics(charHandle, *it); + if( nullptr != decl ) { + return decl; + } + } + return nullptr; +} + +GATTCharacterisicsDeclRef GATTHandler::findCharacterisics(const uint16_t charHandle, GATTPrimaryServiceRef service) { + for(auto it = service->characteristicDeclList.begin(); it != service->characteristicDeclList.end(); it++) { + GATTCharacterisicsDeclRef decl = *it; + if( charHandle == decl->handle ) { + return decl; + } + } + return nullptr; +} + +std::vector<GATTPrimaryServiceRef> & GATTHandler::discoverCompletePrimaryServices() { + if( !discoverPrimaryServices(services) ) { + return services; + } + for(auto it = services.begin(); it != services.end(); it++) { + GATTPrimaryServiceRef primSrv = *it; + if( discoverCharacteristics(primSrv) ) { + discoverClientCharacteristicConfig(primSrv); + } + } + return services; +} + +bool GATTHandler::discoverPrimaryServices(std::vector<GATTPrimaryServiceRef> & result) { + /*** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * + * This sub-procedure is complete when the ATT_ERROR_RSP PDU is received + * and the error code is set to Attribute Not Found or when the End Group Handle + * in the Read by Type Group Response is 0xFFFF. + */ + const uuid16_t groupType = uuid16_t(GattAttributeType::PRIMARY_SERVICE); + + PERF_TS_T0(); + + bool done=false; + uint16_t startHandle=0x0001; + result.clear(); + while(!done) { + const AttReadByNTypeReq req(true /* group */, startHandle, 0xffff, groupType); + DBG_PRINT("GATT PRIM SRV discover send: %s", req.toString().c_str()); + + if( send(req) ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT PRIM SRV discover recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_READ_BY_GROUP_TYPE_RSP ) { + const AttReadByGroupTypeRsp * p = static_cast<const AttReadByGroupTypeRsp*>(pdu.get()); + const int count = p->getElementCount(); + + for(int i=0; i<count; i++) { + const int ePDUOffset = p->getElementPDUOffset(i); + const int esz = p->getElementTotalSize(); + result.push_back( GATTPrimaryServiceRef( new GATTPrimaryService( GATTUUIDHandleRange( + GATTUUIDHandleRange::Type::Service, + p->pdu.get_uint16(ePDUOffset), // start-handle + p->pdu.get_uint16(ePDUOffset + 2), // end-handle + p->pdu.get_uuid( ePDUOffset + 2 + 2, uuid_t::toTypeSize(esz-2-2) ) ) ) ) ); // uuid + DBG_PRINT("GATT PRIM SRV discovered[%d/%d]: %s", i, count, result.at(result.size()-1)->toString().c_str()); + } + startHandle = p->getElementEndHandle(count-1); + if( startHandle < 0xffff ) { + startHandle++; + } else { + done = true; // OK by spec: End of communication + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + done = true; // OK by spec: End of communication + } else { + WARN_PRINT("GATT discoverPrimary unexpected reply %s", pdu->toString().c_str()); + done = true; + } + } else { + ERR_PRINT("GATT discoverPrimary send failed"); + done = true; // send failed + } + } + PERF_TS_TD("GATT discoverPrimaryServices"); + + return result.size() > 0; +} + +bool GATTHandler::discoverCharacteristics(GATTPrimaryServiceRef service) { + /*** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * </p> + */ + const uuid16_t characteristicTypeReq = uuid16_t(GattAttributeType::CHARACTERISTIC); + const uuid16_t clientCharConfigTypeReq = uuid16_t(GattAttributeType::CLIENT_CHARACTERISTIC_CONFIGURATION); + + PERF_TS_T0(); + + bool done=false; + uint16_t handle=service->declaration.startHandle; + service->characteristicDeclList.clear(); + while(!done) { + const AttReadByNTypeReq req(false /* group */, handle, service->declaration.endHandle, characteristicTypeReq); + DBG_PRINT("GATT CCD discover send: %s", req.toString().c_str()); + + if( send(req) ) { + std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT CCD discover recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_READ_BY_TYPE_RSP ) { + const AttReadByTypeRsp * p = static_cast<const AttReadByTypeRsp*>(pdu.get()); + const int count = p->getElementCount(); + + for(int i=0; i<count; i++) { + // handle: handle for the Characteristics declaration + // value: Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID + const int ePDUOffset = p->getElementPDUOffset(i); + const int esz = p->getElementTotalSize(); + service->characteristicDeclList.push_back( GATTCharacterisicsDeclRef( new GATTCharacterisicsDecl( + service->declaration.uuid, + p->pdu.get_uint16(ePDUOffset), // service-handle + service->declaration.endHandle, + static_cast<GATTCharacterisicsDecl::PropertyBitVal>(p->pdu.get_uint8(ePDUOffset + 2)), // properties + p->pdu.get_uint16(ePDUOffset + 2 + 1), // handle + p->pdu.get_uuid(ePDUOffset + 2 + 1 + 2, uuid_t::toTypeSize(esz-2-1-2) ) ) ) ); // uuid + DBG_PRINT("GATT CCD discovered[%d/%d]: %s", i, count, service->characteristicDeclList.at(service->characteristicDeclList.size()-1)->toString().c_str()); + } + handle = p->getElementHandle(count-1); + if( handle < service->declaration.endHandle ) { + handle++; + } else { + done = true; // OK by spec: End of communication + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + done = true; // OK by spec: End of communication + } else { + WARN_PRINT("GATT discoverCharacteristics unexpected reply %s", pdu->toString().c_str()); + done = true; + } + } else { + ERR_PRINT("GATT discoverCharacteristics send failed"); + done = true; + } + } + + PERF_TS_TD("GATT discoverCharacteristics"); + + return service->characteristicDeclList.size() > 0; +} + +bool GATTHandler::discoverClientCharacteristicConfig(GATTPrimaryServiceRef service) { + /*** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * </p> + */ + const uuid16_t clientCharConfigTypeReq = uuid16_t(GattAttributeType::CLIENT_CHARACTERISTIC_CONFIGURATION); + + PERF_TS_T0(); + + bool done=false; + uint16_t handle=service->declaration.startHandle; + // list.clear(); + while(!done) { + const AttReadByNTypeReq req(false /* group */, handle, service->declaration.endHandle, clientCharConfigTypeReq); + DBG_PRINT("GATT CCC discover send: %s", req.toString().c_str()); + + if( send(req) ) { + std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT CCC discover recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_READ_BY_TYPE_RSP ) { + const AttReadByTypeRsp * p = static_cast<const AttReadByTypeRsp*>(pdu.get()); + const int count = p->getElementCount(); + + for(int i=0; i<count; i++) { + // handle: handle for the Characteristics declaration + // value: Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID + const int ePDUOffset = p->getElementPDUOffset(i); + const int esz = p->getElementTotalSize(); + if( 4 == esz ) { + const uint16_t config_handle = p->pdu.get_uint16(ePDUOffset); + const uint16_t config_value = p->pdu.get_uint16(ePDUOffset+2); + for(size_t j=0; j<service->characteristicDeclList.size(); j++) { + GATTCharacterisicsDecl & decl = *service->characteristicDeclList.at(j); + uint16_t decl_handle_end; + if( j+1 < service->characteristicDeclList.size() ) { + decl_handle_end = service->characteristicDeclList.at(j+1)->handle; + } else { + decl_handle_end = decl.service_handle_end; + } + if( config_handle > decl.handle && config_handle <= decl_handle_end ) { + decl.config = std::shared_ptr<GATTClientCharacteristicConfigDecl>( + new GATTClientCharacteristicConfigDecl(config_handle, config_value)); + DBG_PRINT("GATT CCC discovered[%d/%d]: %s", i, count, decl.toString().c_str()); + } + } + } else { + WARN_PRINT("GATT discoverCharacteristicsClientConfig unexpected PDU Element size reply %s", pdu->toString().c_str()); + } + } + handle = p->getElementHandle(count-1); + if( handle < service->declaration.endHandle ) { + handle++; + } else { + done = true; // OK by spec: End of communication + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + done = true; // OK by spec: End of communication + } else { + WARN_PRINT("GATT discoverCharacteristicsClientConfig unexpected opcode reply %s", pdu->toString().c_str()); + done = true; + } + } else { + ERR_PRINT("GATT discoverCharacteristicsClientConfig send failed"); + done = true; + } + } + + PERF_TS_TD("GATT discoverCharacteristicsClientConfig"); + + return service->characteristicDeclList.size() > 0; +} + +bool GATTHandler::discoverCharacteristicDescriptors(const GATTUUIDHandleRange & service, std::vector<GATTUUIDHandle> & result) { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors */ + + PERF_TS_T0(); + + bool done=false; + uint16_t handle=service.startHandle+1; + result.clear(); + while(!done) { + const AttFindInfoReq req(handle, service.endHandle); + DBG_PRINT("GATT CCD discover2 send: %s", req.toString().c_str()); + + if( send(req) ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT CCD discover2 recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_FIND_INFORMATION_RSP ) { + const AttFindInfoRsp * p = static_cast<const AttFindInfoRsp*>(pdu.get()); + const int count = p->getElementCount(); + + for(int i=0; i<count; i++) { + // handle: handle of Characteristic Descriptor Declaration. + // value: Characteristic Descriptor UUID. + result.push_back( GATTUUIDHandle( p->getElementHandle(i), p->getElementValue(i) ) ); + DBG_PRINT("GATT CCD discovered2[%d/%d]: %s", i, count, result.at(result.size()-1).toString().c_str()); + } + handle = p->getElementHandle(count-1); + if( handle < service.endHandle ) { + handle++; + } else { + done = true; // OK by spec: End of communication + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + done = true; // OK by spec: End of communication + } else { + WARN_PRINT("GATT discoverDescriptors unexpected reply %s", pdu->toString().c_str()); + done = true; + } + } else { + ERR_PRINT("GATT discoverDescriptors send failed"); + done = true; + } + } + PERF_TS_TD("GATT discoverDescriptors"); + + return result.size() > 0; +} + +bool GATTHandler::readCharacteristicValue(const GATTCharacterisicsDecl & decl, POctets & res, int expectedLength) { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value */ + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value */ + PERF_TS_T0(); + + bool done=false; + int offset=0; + + DBG_PRINT("GATTHandler::readCharacteristicValue expLen %d, decl %s", expectedLength, decl.toString().c_str()); + + while(!done) { + if( 0 < expectedLength && expectedLength <= offset ) { + break; // done + } else if( 0 == expectedLength && 0 < offset ) { + break; // done w/ only one request + } // else 0 > expectedLength: implicit + + bool sendRes; + + if( 0 == offset ) { + const AttReadReq req (decl.handle); + DBG_PRINT("GATT CV send: %s", req.toString().c_str()); + sendRes = send(req); + } else { + const AttReadBlobReq req (decl.handle, offset); + DBG_PRINT("GATT CV send: %s", req.toString().c_str()); + sendRes = send(req); + } + + if( sendRes ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT CV recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_READ_RSP ) { + const AttReadRsp * p = static_cast<const AttReadRsp*>(pdu.get()); + const TOctetSlice & v = p->getValue(); + res += v; + offset += v.getSize(); + if( p->getPDUValueSize() < p->getMaxPDUValueSize(usedMTU) ) { + done = true; // No full ATT_MTU PDU used - end of communication + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_READ_BLOB_RSP ) { + const AttReadBlobRsp * p = static_cast<const AttReadBlobRsp*>(pdu.get()); + const TOctetSlice & v = p->getValue(); + if( 0 == v.getSize() ) { + done = true; // OK by spec: No more data - end of communication + } else { + res += v; + offset += v.getSize(); + if( p->getPDUValueSize() < p->getMaxPDUValueSize(usedMTU) ) { + done = true; // No full ATT_MTU PDU used - end of communication + } + } + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value + * + * If the Characteristic Value is not longer than (ATT_MTU – 1) + * an ATT_ERROR_RSP PDU with the error + * code set to Attribute Not Long shall be received on the first + * ATT_READ_BLOB_REQ PDU. + */ + const AttErrorRsp * p = static_cast<const AttErrorRsp *>(pdu.get()); + if( AttErrorRsp::ATTRIBUTE_NOT_LONG == p->getErrorCode() ) { + done = true; // OK by spec: No more data - end of communication + } else { + WARN_PRINT("GATT readCharacteristicValue unexpected error %s", pdu->toString().c_str()); + done = true; + } + } else { + WARN_PRINT("GATT readCharacteristicValue unexpected reply %s", pdu->toString().c_str()); + done = true; + } + } else { + ERR_PRINT("GATT readCharacteristicValue send failed"); + done = true; + } + } + PERF_TS_TD("GATT readCharacteristicValue"); + + return offset > 0; +} + +bool GATTHandler::writeClientCharacteristicConfigReq(const GATTClientCharacteristicConfigDecl & cccd, const TROOctets & value) { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */ + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value */ + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.11 Characteristic Value Indication */ + + AttWriteReq req(cccd.handle, value); + DBG_PRINT("GATT send: %s", req.toString().c_str()); + bool res = false; + bool sendRes = send(req); + if( sendRes ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_WRITE_RSP ) { + // OK + res = true; + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + const AttErrorRsp * p = static_cast<const AttErrorRsp *>(pdu.get()); + WARN_PRINT("GATT writeCharacteristicValueReq unexpected error %s", p->toString().c_str()); + } else { + WARN_PRINT("GATT writeCharacteristicValueReq unexpected reply %s", pdu->toString().c_str()); + } + } + return res; +} + +bool GATTHandler::writeCharacteristicValueReq(const GATTCharacterisicsDecl & decl, const TROOctets & value) { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value */ + + DBG_PRINT("GATTHandler::writeCharacteristicValueReq decl %s, value %s", + decl.toString().c_str(), value.toString().c_str()); + + AttWriteReq req(decl.handle, value); + DBG_PRINT("GATT send: %s", req.toString().c_str()); + bool res = false; + bool sendRes = send(req); + if( sendRes ) { + const std::shared_ptr<const AttPDUMsg> pdu = receiveNext(); + DBG_PRINT("GATT recv: %s", pdu->toString().c_str()); + if( pdu->getOpcode() == AttPDUMsg::ATT_WRITE_RSP ) { + // OK + res = true; + } else if( pdu->getOpcode() == AttPDUMsg::ATT_ERROR_RSP ) { + const AttErrorRsp * p = static_cast<const AttErrorRsp *>(pdu.get()); + WARN_PRINT("GATT writeCharacteristicValueReq unexpected error %s", p->toString().c_str()); + } else { + WARN_PRINT("GATT writeCharacteristicValueReq unexpected reply %s", pdu->toString().c_str()); + } + } + return res; +} + + +bool GATTHandler::configIndicationNotification(const GATTClientCharacteristicConfigDecl & cccd, const bool enableNotification, const bool enableIndication) { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */ + const uint16_t ccc_value = enableNotification | ( enableIndication << 1 ); + DBG_PRINT("GATTHandler::configIndicationNotification decl %s, enableNotification %d, enableIndication %d", + cccd.toString().c_str(), enableNotification, enableIndication); + POctets ccc(2); + ccc.put_uint16(0, ccc_value); + return writeClientCharacteristicConfigReq(cccd, ccc); +} + +/*********************************************************************************************************************/ +/*********************************************************************************************************************/ +/*********************************************************************************************************************/ + +static const uuid16_t _GENERIC_ACCESS(GattServiceType::GENERIC_ACCESS); +static const uuid16_t _DEVICE_NAME(GattCharacteristicType::DEVICE_NAME); +static const uuid16_t _APPEARANCE(GattCharacteristicType::APPEARANCE); +static const uuid16_t _PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS(GattCharacteristicType::PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS); + +static const uuid16_t _DEVICE_INFORMATION(GattServiceType::DEVICE_INFORMATION); +static const uuid16_t _SYSTEM_ID(GattCharacteristicType::SYSTEM_ID); +static const uuid16_t _MODEL_NUMBER_STRING(GattCharacteristicType::MODEL_NUMBER_STRING); +static const uuid16_t _SERIAL_NUMBER_STRING(GattCharacteristicType::SERIAL_NUMBER_STRING); +static const uuid16_t _FIRMWARE_REVISION_STRING(GattCharacteristicType::FIRMWARE_REVISION_STRING); +static const uuid16_t _HARDWARE_REVISION_STRING(GattCharacteristicType::HARDWARE_REVISION_STRING); +static const uuid16_t _SOFTWARE_REVISION_STRING(GattCharacteristicType::SOFTWARE_REVISION_STRING); +static const uuid16_t _MANUFACTURER_NAME_STRING(GattCharacteristicType::MANUFACTURER_NAME_STRING); +static const uuid16_t _REGULATORY_CERT_DATA_LIST(GattCharacteristicType::REGULATORY_CERT_DATA_LIST); +static const uuid16_t _PNP_ID(GattCharacteristicType::PNP_ID); + +std::shared_ptr<GenericAccess> GATTHandler::getGenericAccess(std::vector<GATTCharacterisicsDeclRef> & genericAccessCharDeclList) { + std::shared_ptr<GenericAccess> res = nullptr; + POctets value(GATTHandler::ClientMaxMTU, 0); + std::string deviceName = ""; + GenericAccess::AppearanceCat category = GenericAccess::AppearanceCat::UNKNOWN; + PeriphalPreferredConnectionParameters * prefConnParam = nullptr; + + for(size_t i=0; i<genericAccessCharDeclList.size(); i++) { + const GATTCharacterisicsDecl & charDecl = *genericAccessCharDeclList.at(i); + if( _GENERIC_ACCESS != *charDecl.service_uuid ) { + continue; + } + if( _DEVICE_NAME == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + deviceName = GattNameToString(value); + } + } else if( _APPEARANCE == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + category = static_cast<GenericAccess::AppearanceCat>(value.get_uint16(0)); + } + } else if( _PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + prefConnParam = new PeriphalPreferredConnectionParameters(value); + } + } + } + if( deviceName.size() > 0 && nullptr != prefConnParam ) { + res = std::shared_ptr<GenericAccess>(new GenericAccess(deviceName, category, *prefConnParam)); + } + if( nullptr != prefConnParam ) { + delete prefConnParam; + } + return res; +} + +std::shared_ptr<GenericAccess> GATTHandler::getGenericAccess(std::vector<GATTPrimaryServiceRef> & primServices) { + std::shared_ptr<GenericAccess> res = nullptr; + for(size_t i=0; i<primServices.size() && nullptr == res; i++) { + res = getGenericAccess(primServices.at(i)->characteristicDeclList); + } + return res; +} + +std::shared_ptr<DeviceInformation> GATTHandler::getDeviceInformation(std::vector<GATTCharacterisicsDeclRef> & characteristicDeclList) { + std::shared_ptr<DeviceInformation> res = nullptr; + POctets value(GATTHandler::ClientMaxMTU, 0); + + POctets systemID(8, 0); + std::string modelNumber; + std::string serialNumber; + std::string firmwareRevision; + std::string hardwareRevision; + std::string softwareRevision; + std::string manufacturer; + POctets regulatoryCertDataList(128, 0); + PnP_ID * pnpID = nullptr; + bool found = false; + + for(size_t i=0; i<characteristicDeclList.size(); i++) { + const GATTCharacterisicsDecl & charDecl = *characteristicDeclList.at(i); + if( _DEVICE_INFORMATION != *charDecl.service_uuid ) { + continue; + } + found = true; + if( _SYSTEM_ID == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, systemID.resize(0)) ) { + // nop + } + } else if( _REGULATORY_CERT_DATA_LIST == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, regulatoryCertDataList.resize(0)) ) { + // nop + } + } else if( _PNP_ID == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + pnpID = new PnP_ID(value); + } + } else if( _MODEL_NUMBER_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + modelNumber = GattNameToString(value); + } + } else if( _SERIAL_NUMBER_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + serialNumber = GattNameToString(value); + } + } else if( _FIRMWARE_REVISION_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + firmwareRevision = GattNameToString(value); + } + } else if( _HARDWARE_REVISION_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + hardwareRevision = GattNameToString(value); + } + } else if( _SOFTWARE_REVISION_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + softwareRevision = GattNameToString(value); + } + } else if( _MANUFACTURER_NAME_STRING == *charDecl.uuid ) { + if( readCharacteristicValue(charDecl, value.resize(0)) ) { + manufacturer = GattNameToString(value); + } + } + } + if( nullptr == pnpID ) { + pnpID = new PnP_ID(); + } + if( found ) { + res = std::shared_ptr<DeviceInformation>(new DeviceInformation(systemID, modelNumber, serialNumber, + firmwareRevision, hardwareRevision, softwareRevision, + manufacturer, regulatoryCertDataList, *pnpID) ); + } + delete pnpID; + return res; +} + +std::shared_ptr<DeviceInformation> GATTHandler::getDeviceInformation(std::vector<GATTPrimaryServiceRef> & primServices) { + std::shared_ptr<DeviceInformation> res = nullptr; + for(size_t i=0; i<primServices.size() && nullptr == res; i++) { + res = getDeviceInformation(primServices.at(i)->characteristicDeclList); + } + return res; +} + diff --git a/src/direct_bt/GATTNumbers.cpp b/src/direct_bt/GATTNumbers.cpp new file mode 100644 index 00000000..970d94e2 --- /dev/null +++ b/src/direct_bt/GATTNumbers.cpp @@ -0,0 +1,507 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "GATTNumbers.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.generic_access.xml */ +const GattServiceCharacteristic direct_bt::GATT_GENERIC_ACCESS_SRVC = { GENERIC_ACCESS, + { { DEVICE_NAME, Mandatory, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Optional }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { APPEARANCE, Mandatory, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { PERIPHERAL_PRIVACY_FLAG, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, C1 }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { RECONNECTION_ADDRESS, Conditional, + // GattCharacteristicPropertySpec[9]: + { { Read, Excluded }, + { WriteWithAck, Mandatory }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + } }; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.health_thermometer.xml */ +const GattServiceCharacteristic direct_bt::GATT_HEALTH_THERMOMETER_SRVC = { HEALTH_THERMOMETER, + { { TEMPERATURE_MEASUREMENT, Mandatory, + // GattCharacteristicPropertySpec[9]: + { { Read, Excluded }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Mandatory, { Read, Mandatory}, { WriteWithAck, Mandatory } } + }, + { TEMPERATURE_TYPE, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { INTERMEDIATE_TEMPERATURE, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Excluded }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Mandatory }, { Indicate, Excluded }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { if_characteristic_supported, { Read, Mandatory}, { WriteWithAck, Mandatory } } + }, + { MEASUREMENT_INTERVAL, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Optional }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Optional }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { if_notify_or_indicate_supported, { Read, Mandatory}, { WriteWithAck, Mandatory } } + }, + } }; + +const GattServiceCharacteristic direct_bt::GATT_DEVICE_INFORMATION_SRVC = { DEVICE_INFORMATION, + { { MANUFACTURER_NAME_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { MODEL_NUMBER_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { SERIAL_NUMBER_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { HARDWARE_REVISION_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { FIRMWARE_REVISION_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { SOFTWARE_REVISION_STRING, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { SYSTEM_ID, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { REGULATORY_CERT_DATA_LIST, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + }, + { PNP_ID, Optional, + // GattCharacteristicPropertySpec[9]: + { { Read, Mandatory }, + { WriteWithAck, Excluded }, { WriteNoAck, Excluded }, { AuthSignedWrite, Excluded }, { ReliableWriteExt, Excluded }, + { Notify, Excluded }, { Indicate, Mandatory }, { AuxWriteExt, Excluded }, { Broadcast, Excluded } }, + // GattClientCharacteristicConfigSpec: + { Excluded, { Read, Excluded}, { WriteWithAck, Excluded } } + } + } }; + +const std::vector<const GattServiceCharacteristic*> direct_bt::GATT_SERVICES = { + &direct_bt::GATT_GENERIC_ACCESS_SRVC, &direct_bt::GATT_HEALTH_THERMOMETER_SRVC, &direct_bt::GATT_DEVICE_INFORMATION_SRVC }; + +#define CASE_TO_STRING(V) case V: return #V; + +#define SERVICE_TYPE_ENUM(X) \ + X(GENERIC_ACCESS) \ + X(HEALTH_THERMOMETER) \ + X(DEVICE_INFORMATION) \ + X(BATTERY_SERVICE) + +std::string direct_bt::GattServiceTypeToString(const GattServiceType v) { + switch(v) { + SERVICE_TYPE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown"; +} + +#define CHARACTERISTIC_TYPE_ENUM(X) \ + X(DEVICE_NAME) \ + X(APPEARANCE) \ + X(PERIPHERAL_PRIVACY_FLAG) \ + X(RECONNECTION_ADDRESS) \ + X(PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS) \ + X(TEMPERATURE) \ + X(TEMPERATURE_CELSIUS) \ + X(TEMPERATURE_FAHRENHEIT) \ + X(TEMPERATURE_MEASUREMENT) \ + X(TEMPERATURE_TYPE) \ + X(INTERMEDIATE_TEMPERATURE) \ + X(MEASUREMENT_INTERVAL) \ + X(SYSTEM_ID) \ + X(MODEL_NUMBER_STRING) \ + X(SERIAL_NUMBER_STRING) \ + X(FIRMWARE_REVISION_STRING) \ + X(HARDWARE_REVISION_STRING) \ + X(SOFTWARE_REVISION_STRING) \ + X(MANUFACTURER_NAME_STRING) \ + X(REGULATORY_CERT_DATA_LIST) \ + X(PNP_ID) + + +std::string direct_bt::GattCharacteristicTypeToString(const GattCharacteristicType v) { + switch(v) { + CHARACTERISTIC_TYPE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown"; +} + +#define CHARACTERISTIC_PROP_ENUM(X) \ + X(Broadcast) \ + X(Read) \ + X(WriteNoAck) \ + X(WriteWithAck) \ + X(Notify) \ + X(Indicate) \ + X(AuthSignedWrite) \ + X(ExtProps) \ + X(ReliableWriteExt) \ + X(AuxWriteExt) + +std::string direct_bt::GattCharacteristicPropertyToString(const GattCharacteristicProperty v) { + switch(v) { + CHARACTERISTIC_PROP_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown"; +} + +#define REQUIREMENT_SPEC_ENUM(X) \ + X(Excluded) \ + X(Mandatory) \ + X(Optional) \ + X(Conditional) \ + X(if_characteristic_supported) \ + X(if_notify_or_indicate_supported) \ + X(C1) + +std::string direct_bt::GattRequirementSpecToString(const GattRequirementSpec v) { + switch(v) { + REQUIREMENT_SPEC_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown"; +} + +std::string GattCharacteristicPropertySpec::toString() const { + return GattCharacteristicPropertyToString(property)+": "+GattRequirementSpecToString(requirement); +} + +std::string GattClientCharacteristicConfigSpec::toString() const { + return "ClientCharCfg["+GattRequirementSpecToString(requirement)+"["+read.toString()+", "+writeWithAck.toString()+"]]"; +} + +std::string GattCharacteristicSpec::toString() const { + std::string res = GattCharacteristicTypeToString(characteristic)+": "+GattRequirementSpecToString(requirement)+", Properties["; + for(size_t i=0; i<propertySpec.size(); i++) { + if(0<i) { + res += ", "; + } + res += propertySpec.at(i).toString(); + } + res += "], "+clientConfig.toString(); + return res; +} + +std::string GattServiceCharacteristic::toString() const { + std::string res = GattServiceTypeToString(service)+": ["; + for(size_t i=0; i<characteristics.size(); i++) { + if(0<i) { + res += ", "; + } + res += "["+characteristics.at(i).toString()+"]"; + } + res += "]"; + return res; +} + +const GattServiceCharacteristic * direct_bt::findGattServiceChar(const uint16_t uuid16) { + for(size_t i=0; i<GATT_SERVICES.size(); i++) { + const GattServiceCharacteristic & serviceChar = *GATT_SERVICES.at(i); + if( uuid16 == serviceChar.service ) { + return &serviceChar; + } + for(size_t j=0; j<serviceChar.characteristics.size(); j++) { + const GattCharacteristicSpec & charSpec = serviceChar.characteristics.at(i); + if( uuid16 == charSpec.characteristic ) { + return &serviceChar; + } + } + } + return nullptr; +} + +const GattCharacteristicSpec * direct_bt::findGattCharSpec(const uint16_t uuid16) { + for(size_t i=0; i<GATT_SERVICES.size(); i++) { + const GattServiceCharacteristic & serviceChar = *GATT_SERVICES.at(i); + for(size_t j=0; j<serviceChar.characteristics.size(); j++) { + const GattCharacteristicSpec & charSpec = serviceChar.characteristics.at(i); + if( uuid16 == charSpec.characteristic ) { + return &charSpec; + } + } + } + return nullptr; +} + +/********************************************************/ +/********************************************************/ +/********************************************************/ + +std::string direct_bt::GattNameToString(const TROOctets &v) { + const int str_len = v.getSize(); + POctets s(str_len+1); // dtor releases chunk + memcpy(s.get_wptr(), v.get_ptr(), str_len); + s.put_uint8(str_len, 0); // EOS + return std::string((const char*)s.get_ptr()); +} + +PeriphalPreferredConnectionParameters::PeriphalPreferredConnectionParameters(const TROOctets &source) +: minConnectionInterval(source.get_uint16(0)), maxConnectionInterval(source.get_uint16(2)), + slaveLatency(source.get_uint16(4)), connectionSupervisionTimeoutMultiplier(source.get_uint16(6)) +{ +} + +std::string PeriphalPreferredConnectionParameters::toString() const { + return "PrefConnectionParam[interval["+ + std::to_string(minConnectionInterval)+".."+std::to_string(maxConnectionInterval)+ + "], slaveLatency "+std::to_string(slaveLatency)+ + ", csTimeoutMul "+std::to_string(connectionSupervisionTimeoutMultiplier)+"]"; +} + +#define GENERIC_ACCESS_APPEARANCECAT_ENUM(X) \ + X(UNKNOWN) \ + X(GENERIC_PHONE) \ + X(GENERIC_COMPUTER) \ + X(GENERIC_WATCH) \ + X(SPORTS_WATCH) \ + X(GENERIC_CLOCK) \ + X(GENERIC_DISPLAY) \ + X(GENERIC_REMOTE_CLOCK) \ + X(GENERIC_EYE_GLASSES) \ + X(GENERIC_TAG) \ + X(GENERIC_KEYRING) \ + X(GENERIC_MEDIA_PLAYER) \ + X(GENERIC_BARCODE_SCANNER) \ + X(GENERIC_THERMOMETER) \ + X(GENERIC_THERMOMETER_EAR) \ + X(GENERIC_HEART_RATE_SENSOR) \ + X(HEART_RATE_SENSOR_BELT) \ + X(GENERIC_BLOD_PRESSURE) \ + X(BLOD_PRESSURE_ARM) \ + X(BLOD_PRESSURE_WRIST) \ + X(HID) \ + X(HID_KEYBOARD) \ + X(HID_MOUSE) \ + X(HID_JOYSTICK) \ + X(HID_GAMEPAD) \ + X(HID_DIGITIZER_TABLET) \ + X(HID_CARD_READER) \ + X(HID_DIGITAL_PEN) \ + X(HID_BARCODE_SCANNER) \ + X(GENERIC_GLUCOSE_METER) \ + X(GENERIC_RUNNING_WALKING_SENSOR) \ + X(RUNNING_WALKING_SENSOR_IN_SHOE) \ + X(RUNNING_WALKING_SENSOR_ON_SHOE) \ + X(RUNNING_WALKING_SENSOR_HIP) \ + X(GENERIC_CYCLING) \ + X(CYCLING_COMPUTER) \ + X(CYCLING_SPEED_SENSOR) \ + X(CYCLING_CADENCE_SENSOR) \ + X(CYCLING_POWER_SENSOR) \ + X(CYCLING_SPEED_AND_CADENCE_SENSOR) \ + X(GENERIC_PULSE_OXIMETER) \ + X(PULSE_OXIMETER_FINGERTIP) \ + X(PULSE_OXIMETER_WRIST) \ + X(GENERIC_WEIGHT_SCALE) \ + X(GENERIC_PERSONAL_MOBILITY_DEVICE) \ + X(PERSONAL_MOBILITY_DEVICE_WHEELCHAIR) \ + X(PERSONAL_MOBILITY_DEVICE_SCOOTER) \ + X(GENERIC_CONTINUOUS_GLUCOSE_MONITOR) \ + X(GENERIC_INSULIN_PUMP) \ + X(INSULIN_PUMP_DURABLE) \ + X(INSULIN_PUMP_PATCH) \ + X(INSULIN_PUMP_PEN) \ + X(GENERIC_MEDICATION_DELIVERY) \ + X(GENERIC_OUTDOOR_SPORTS_ACTIVITY) \ + X(OUTDOOR_SPORTS_ACTIVITY_LOCATION_DISPLAY_DEVICE) \ + X(OUTDOOR_SPORTS_ACTIVITY_LOCATION_AND_NAVIGATION_DISPLAY_DEVICE) \ + X(OUTDOOR_SPORTS_ACTIVITY_LOCATION_POD) \ + X(OUTDOOR_SPORTS_ACTIVITY_LOCATION_AND_NAVIGATION_POD) \ + +std::string GenericAccess::AppearanceCatToString(const AppearanceCat v) { + switch(v) { + GENERIC_ACCESS_APPEARANCECAT_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown"; +} + +std::string GenericAccess::toString() const { + return "'"+deviceName+"'[cat "+AppearanceCatToString(category)+", "+prefConnParam.toString()+"]"; +} + +PnP_ID::PnP_ID(const TROOctets &source) +: vendor_id_source(source.get_uint8(0)), vendor_id(source.get_uint16(1)), + product_id(source.get_uint16(3)), product_version(source.get_uint16(5)) {} + +std::string PnP_ID::toString() const { + return "vendor_id[source "+uint8HexString(vendor_id_source, true)+ + ", id "+uint16HexString(vendor_id, true)+ + "], product_id "+uint16HexString(product_id, true)+ + ", product_version "+uint16HexString(product_version, true); +} + +std::string DeviceInformation::toString() const { + return "DeviceInfo[manufacturer '"+manufacturer+"', model '"+modelNumber+"', serial '"+serialNumber+"', systemID '"+systemID.toString()+ + "', revisions[firmware '"+firmwareRevision+"', hardware '"+hardwareRevision+"', software '"+softwareRevision+ + "'], pnpID["+pnpID.toString()+"], regCertData '"+regulatoryCertDataList.toString()+"']"; +} + +std::shared_ptr<TemperatureMeasurementCharateristic> TemperatureMeasurementCharateristic::get(const TROOctets &source) { + const int size = source.getSize(); + int reqSize = 1 + 4; // max size = 13 + if( reqSize > size ) { + // min size: flags + temperatureValue + return nullptr; + } + + uint8_t flags = source.get_uint8(0); + bool hasTimestamp = 0 != ( flags & Bits::HAS_TIMESTAMP ); + if( hasTimestamp ) { + reqSize += 7; + } + bool hasTemperatureType = 0 != ( flags & Bits::HAS_TEMP_TYPE ); + if( hasTemperatureType ) { + reqSize += 1; + } + if( reqSize > size ) { + return nullptr; + } + + uint32_t raw_temp_value = source.get_uint32(1); + float temperatureValue = ieee11073::FloatTypes::float32_IEEE11073_to_IEEE754(raw_temp_value); + + /** Timestamp, if HAS_TIMESTAMP is set. */ + ieee11073::AbsoluteTime timestamp; + if( hasTemperatureType ) { + timestamp = ieee11073::AbsoluteTime(source.get_ptr(1+4), 7); + } + + /** Temperature Type, if HAS_TEMP_TYPE is set: Format ???? */ + uint8_t temperature_type=0; + if( hasTemperatureType ) { + temperature_type = source.get_uint8(1+4+7); + } + return std::shared_ptr<TemperatureMeasurementCharateristic>(new TemperatureMeasurementCharateristic(flags, temperatureValue, timestamp, temperature_type)); +} + +std::string TemperatureMeasurementCharateristic::toString() const { + std::string res = std::to_string(temperatureValue); + res += isFahrenheit() ? " F" : " C"; + if( hasTimestamp() ) { + res += ", "+timestamp.toString(); + } + if( hasTemperatureType() ) { + res += ", type "+std::to_string(temperature_type); + } + return res; +} diff --git a/src/direct_bt/GATTTypes.cpp b/src/direct_bt/GATTTypes.cpp new file mode 100644 index 00000000..14a5d3dd --- /dev/null +++ b/src/direct_bt/GATTTypes.cpp @@ -0,0 +1,114 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "GATTTypes.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +#define CHAR_DECL_PROPS_ENUM(X) \ + X(Broadcast) \ + X(Read) \ + X(WriteNoAck) \ + X(WriteWithAck) \ + X(Notify) \ + X(Indicate) \ + X(AuthSignedWrite) \ + X(ExtProps) + +#define CASE_TO_STRING(V) case V: return #V; + +std::string GATTCharacterisicsDecl::getPropertyString(const PropertyBitVal prop) { + switch(prop) { + CHAR_DECL_PROPS_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown property"; +} + +std::string GATTCharacterisicsDecl::getPropertiesString(const PropertyBitVal properties) { + std::string res = "["; + int i = 0; + if( 0 != ( properties & Broadcast ) ) { + i++; + res += getPropertyString(Broadcast); + } + if( 0 != ( properties & Read ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(Read); + } + if( 0 != ( properties & WriteNoAck ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(WriteNoAck); + } + if( 0 != ( properties & WriteWithAck ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(WriteWithAck); + } + if( 0 != ( properties & Notify ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(Notify); + } + if( 0 != ( properties & Indicate ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(Indicate); + } + if( 0 != ( properties & AuthSignedWrite ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(AuthSignedWrite); + } + if( 0 != ( properties & ExtProps ) ) { + if( 0 < i++ ) { + res += ", "; + } + res += getPropertyString(ExtProps); + } + return res+"]"; +} + + + diff --git a/src/direct_bt/HCIAdapter.cpp b/src/direct_bt/HCIAdapter.cpp new file mode 100644 index 00000000..b1baeead --- /dev/null +++ b/src/direct_bt/HCIAdapter.cpp @@ -0,0 +1,391 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "BTIoctl.hpp" +#include "HCIIoctl.hpp" + +#include "HCIComm.hpp" +#include "HCITypes.hpp" + +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <poll.h> +} + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +// ************************************************* +// ************************************************* +// ************************************************* + +std::atomic_int HCISession::name_counter(0); + +void HCISession::disconnect(const uint8_t reason) { + connectedDevice = nullptr; + + if( !hciComm.isLEConnected() ) { + return; + } + if( !hciComm.le_disconnect(reason) ) { + DBG_PRINT("HCISession::disconnect: errno %d %s", errno, strerror(errno)); + } +} + +bool HCISession::close() +{ + if( !hciComm.isOpen() ) { + return false; + } + hciComm.close(); + adapter.sessionClosed(*this); + return true; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +bool HCIAdapter::validateDevInfo() { + if( !mgmt.isOpen() || 0 > dev_id ) { + return false; + } + + adapterInfo = mgmt.getAdapter(dev_id); + return true; +} + +void HCIAdapter::sessionClosed(HCISession& s) +{ + session = nullptr; +} + +HCIAdapter::HCIAdapter() +: mgmt(MgmtHandler::get()), dev_id(mgmt.getDefaultAdapterIdx()) +{ + valid = validateDevInfo(); +} + +HCIAdapter::HCIAdapter(EUI48 &mac) +: mgmt(MgmtHandler::get()), dev_id(mgmt.findAdapterIdx(mac)) +{ + valid = validateDevInfo(); +} + +HCIAdapter::HCIAdapter(const int dev_id) +: mgmt(MgmtHandler::get()), dev_id(dev_id) +{ + valid = validateDevInfo(); +} + +HCIAdapter::~HCIAdapter() { + scannedDevices.clear(); + discoveredDevices.clear(); + session = nullptr; +} + +std::shared_ptr<HCISession> HCIAdapter::open() +{ + if( !valid ) { + return nullptr; + } + HCISession * s = new HCISession( *this, dev_id, HCI_CHANNEL_RAW ); + if( !s->isOpen() ) { + delete s; + perror("Could not open device"); + return nullptr; + } + session = std::shared_ptr<HCISession>( s ); + return session; +} + +std::shared_ptr<HCIDeviceDiscoveryListener> HCIAdapter::setDeviceDiscoveryListener(std::shared_ptr<HCIDeviceDiscoveryListener> l) +{ + std::shared_ptr<HCIDeviceDiscoveryListener> o = deviceDiscoveryListener; + deviceDiscoveryListener = l; + return o; +} + +bool HCIAdapter::startDiscovery(HCISession &session, uint8_t own_mac_type, + uint16_t interval, uint16_t window) +{ + if( !session.isOpen() ) { + fprintf(stderr, "Session not open\n"); + return false; + } + if( !session.hciComm.le_enable_scan(own_mac_type, interval, window) ) { + perror("Start scanning failed"); + return false; + } + return true; +} + +void HCIAdapter::stopDiscovery(HCISession& session) { + if( !session.isOpen() ) { + return; + } + session.hciComm.le_disable_scan(); +} + +int HCIAdapter::findDevice(std::vector<std::shared_ptr<HCIDevice>> const & devices, EUI48 const & mac) { + auto begin = devices.begin(); + auto it = std::find_if(begin, devices.end(), [&](std::shared_ptr<HCIDevice> const& p) { + return p->mac == mac; + }); + if ( it == std::end(devices) ) { + return -1; + } else { + return std::distance(begin, it); + } +} + +int HCIAdapter::findScannedDeviceIdx(EUI48 const & mac) const { + return findDevice(scannedDevices, mac); +} + +std::shared_ptr<HCIDevice> HCIAdapter::findScannedDevice (EUI48 const & mac) const { + const int idx = findDevice(scannedDevices, mac); + if( 0 <= idx ) { + return scannedDevices.at(idx); + } + return nullptr; +} + +bool HCIAdapter::addScannedDevice(std::shared_ptr<HCIDevice> const &device) { + if( 0 > findDevice(scannedDevices, device->mac) ) { + scannedDevices.push_back(device); + return true; + } + return false; +} + +int HCIAdapter::findDiscoveredDeviceIdx(EUI48 const & mac) const { + return findDevice(discoveredDevices, mac); +} + +std::shared_ptr<HCIDevice> HCIAdapter::findDiscoveredDevice (EUI48 const & mac) const { + const int idx = findDevice(discoveredDevices, mac); + if( 0 <= idx ) { + return discoveredDevices.at(idx); + } + return nullptr; +} + +bool HCIAdapter::addDiscoveredDevice(std::shared_ptr<HCIDevice> const &device) { + if( 0 > findDiscoveredDeviceIdx(device->mac) ) { + discoveredDevices.push_back(device); + return true; + } + return false; +} + +void HCIAdapter::removeDiscoveredDevices() { + // also need to flush scannedDevices, old data + scannedDevices.clear(); + discoveredDevices.clear(); +} + +std::string HCIAdapter::toString() const { + std::string out("Adapter["+getAddressString()+", '"+getName()+"', id="+std::to_string(dev_id)+"]"); + if(discoveredDevices.size() > 0 ) { + out.append("\n"); + for(auto it = discoveredDevices.begin(); it != discoveredDevices.end(); it++) { + std::shared_ptr<HCIDevice> p = *it; + out.append(" ").append(p->toString()).append("\n"); + } + } + return out; +} + +// ************************************************* + +int HCIAdapter::discoverDevices(HCISession& session, + const int waitForDeviceCount, + const EUI48 &waitForDevice, + const int timeoutMS, + const uint32_t ad_type_req) +{ + uint8_t buf[HCI_MAX_EVENT_SIZE]; + hci_ufilter nf, of; + socklen_t olen; + int bytes_left = -1; + const int64_t t0 = getCurrentMilliseconds(); + int err; + + if( !session.isOpen() ) { + fprintf(stderr, "Session not open\n"); + return false; + } + + olen = sizeof(of); + if (getsockopt(session.dd(), SOL_HCI, HCI_FILTER, &of, &olen) < 0) { + perror("Could not get socket options"); + return false; + } + + HCIComm::filter_clear(&nf); + HCIComm::filter_set_ptype(HCI_EVENT_PKT, &nf); + HCIComm::filter_set_event(HCI_EV_LE_META, &nf); + + if (setsockopt(session.dd(), SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) { + perror("Could not set socket options"); + return false; + } + + const uint32_t ad_req = static_cast<uint32_t>(EInfoReport::Element::BDADDR) | + static_cast<uint32_t>(EInfoReport::Element::RSSI) | + ad_type_req; + bool done = false; + int64_t t1; + int matchedDeviceCount = 0, loop=0; + + while ( !done && ( ( t1 = getCurrentMilliseconds() ) - t0 ) < timeoutMS ) { + uint8_t hci_type; + hci_event_hdr *ehdr; + hci_ev_le_meta *meta; + loop++; + + if( timeoutMS ) { + struct pollfd p; + int n; + + p.fd = session.dd(); p.events = POLLIN; + while ((n = poll(&p, 1, timeoutMS)) < 0) { + if (errno == EAGAIN || errno == EINTR ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + if (!n) { + // A timeout is not considered an error for discovery. + // errno = ETIMEDOUT; + // goto errout; + goto done; + } + } + + while ((bytes_left = read(session.dd(), buf, sizeof(buf))) < 0) { + if (errno == EAGAIN || errno == EINTR ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + + if( bytes_left < HCI_TYPE_LEN + (int)sizeof(hci_event_hdr) + (int)sizeof(hci_ev_le_meta) ) { + // not enough data .. + continue; + } + hci_type = buf[0]; // sizeof HCI_TYPE_LEN + + ehdr = (hci_event_hdr*)(void*) ( buf + HCI_TYPE_LEN ); // sizeof hci_event_hdr + + bytes_left -= (HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE); + meta = (hci_ev_le_meta*)(void *) ( buf + ( HCI_TYPE_LEN + (int)sizeof(hci_event_hdr) ) ); // sizeof hci_ev_le_meta + + if( bytes_left < ehdr->plen ) { + // not enough data .. + fprintf(stderr, "HCIAdapter::discovery[%d]: Warning: Incomplete type 0x%.2X, event 0x%.2X, subevent 0x%.2X, remaining %d bytes < plen %d!\n", + loop-1, hci_type, ehdr->evt, meta->subevent, bytes_left, ehdr->plen); + continue; + } else { + DBG_PRINT("HCIAdapter::discovery[%d]: Complete type 0x%.2X, event 0x%.2X, subevent 0x%.2X, remaining %d bytes >= plen %d", + loop-1, hci_type, ehdr->evt, meta->subevent, bytes_left, ehdr->plen); + } + + // HCI_LE_Advertising_Report == 0x3E == HCI_EV_LE_META + // 0x3E 0x02 + if ( HCI_Event_Types::LE_Advertising_Report != ehdr->evt || meta->subevent != HCI_EV_LE_ADVERTISING_REPORT ) { + continue; // next .. + } + bytes_left -= sizeof(hci_ev_le_meta); + + std::vector<std::shared_ptr<EInfoReport>> ad_reports = EInfoReport::read_ad_reports(((uint8_t*)(void *)meta)+1, bytes_left); + const int num_reports = ad_reports.size(); + + for(int i = 0; i < num_reports && i < 0x19; i++) { + std::shared_ptr<EInfoReport> ad_report = ad_reports.at(i); + const bool matches = ( ad_req == ( ad_req & ad_report->getDataSet() ) ) && + ( EUI48_ANY_DEVICE == waitForDevice || ad_report->getAddress() == waitForDevice ); + if( matches ) { + matchedDeviceCount++; + if( 0 < waitForDeviceCount && waitForDeviceCount <= matchedDeviceCount ) { + done = true; + } + } + DBG_PRINT("HCIAdapter::discovery[%d] %d/%d: matches %d, waitForDevice %s, ad_req %s, matchCount %d/%d, done %d", + loop-1, i, num_reports, matches, waitForDevice.toString().c_str(), EInfoReport::dataSetToString(ad_req).c_str(), + matchedDeviceCount, waitForDeviceCount, done); + DBG_PRINT("HCIAdapter::discovery[%d] %d/%d: %s", loop-1, i, num_reports, ad_report->toString().c_str()); + + int idx = findDevice(scannedDevices, ad_report->getAddress()); + std::shared_ptr<HCIDevice> dev; + if( 0 > idx ) { + // new device + dev = std::shared_ptr<HCIDevice>(new HCIDevice(*this, *ad_report)); + scannedDevices.push_back(dev); + } else { + // existing device + dev = scannedDevices.at(idx); + dev->update(*ad_report); + } + if( matches ) { + if( addDiscoveredDevice(dev) ) { + // new matching + if( nullptr != deviceDiscoveryListener ) { + deviceDiscoveryListener->deviceAdded(*this, dev); + } + } else { + // update + if( nullptr != deviceDiscoveryListener ) { + deviceDiscoveryListener->deviceUpdated(*this, dev); + } + } + } + } + + } +done: + setsockopt(session.dd(), SOL_HCI, HCI_FILTER, &of, sizeof(of)); + return matchedDeviceCount; + +errout: + err = errno; + setsockopt(session.dd(), SOL_HCI, HCI_FILTER, &of, sizeof(of)); + errno = err; + return -1; +} diff --git a/src/direct_bt/HCIComm.cpp b/src/direct_bt/HCIComm.cpp new file mode 100644 index 00000000..0756357f --- /dev/null +++ b/src/direct_bt/HCIComm.cpp @@ -0,0 +1,550 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +// #define VERBOSE_ON 1 + +#include "HCIComm.hpp" + +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <sys/param.h> + #include <sys/uio.h> + #include <sys/types.h> + #include <sys/ioctl.h> + #include <sys/socket.h> + #include <poll.h> +} + +#include "dbt_debug.hpp" + +namespace direct_bt { + +int HCIComm::hci_open_dev(const uint16_t dev_id, const uint16_t channel) +{ + struct sockaddr_hci a; + int dd, err; + + if (0 > dev_id ) { + errno = ENODEV; + ERR_PRINT("hci_open_dev: invalid dev_id errno %d %s", errno, strerror(errno)); + return -1; + } + + // Create a loose HCI socket + dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (0 > dd ) { + perror("HCIComm::hci_open_dev: socket failed"); + return dd; + } + + // Bind socket to the HCI device + bzero(&a, sizeof(a)); + a.hci_family = AF_BLUETOOTH; + a.hci_dev = dev_id; + a.hci_channel = channel; + if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0) { + ERR_PRINT("hci_open_dev: bind failed errno %d %s", errno, strerror(errno)); + goto failed; + } + + return dd; + +failed: + err = errno; + ::close(dd); + errno = err; + + return -1; +} + +int HCIComm::hci_close_dev(int dd) +{ + return ::close(dd); +} + +void HCIComm::close() { + if( 0 > _dd ) { + return; + } + hci_close_dev(_dd); + _dd = -1; +} + + +bool HCIComm::send_cmd(const uint16_t opcode, const void *command, const uint8_t command_len) +{ + if( 0 > _dd ) { + ERR_PRINT("hci_send_cmd: device not open"); + return false; + } + const uint8_t type = HCI_COMMAND_PKT; + hci_command_hdr hc; + struct iovec iv[3]; + int ivn; + int bw=0; + + hc.opcode = htobs(opcode); + hc.plen= command_len; + + iv[0].iov_base = (void*)&type; + iv[0].iov_len = 1; + iv[1].iov_base = (void*)&hc; + iv[1].iov_len = HCI_COMMAND_HDR_SIZE; + ivn = 2; + + if (command_len) { + iv[2].iov_base = (void*)command; + iv[2].iov_len = command_len; + ivn = 3; + } + +#ifdef VERBOSE_ON + { + fprintf(stderr, "hci_send_cmd: type 0x%2.2X, opcode 0x%X, plen %d\n", type, hc.opcode, command_len); + std::string paramstr = command_len > 0 ? bytesHexString((uint8_t*)command, 0, command_len, false /* lsbFirst */, true /* leading0X */) : ""; + fprintf(stderr, "hci_send_cmd: param: %s\n", paramstr.c_str()); + } +#endif + + while ( ( bw = writev(_dd, iv, ivn) ) < 0 ) { + ERR_PRINT("hci_send_cmd: writev res %d, errno %d %s", bw, errno, strerror(errno)); + if (errno == EAGAIN || errno == EINTR) { + continue; + } + return false; + } + DBG_PRINT("hci_send_cmd: writev: %d", bw); + return true; +} + +#define _HCI_PKT_TRY_COUNT 10 + +bool HCIComm::send_req(const uint16_t opcode, const void *command, const uint8_t command_len, + const uint16_t exp_event, void *response, const uint8_t response_len) +{ + if( 0 > _dd ) { + ERR_PRINT("hci_send_req: device not open"); + return false; + } + uint8_t buf[HCI_MAX_EVENT_SIZE]; + const uint16_t opcode_le16 = htobs(opcode); + hci_ufilter nf, of; + socklen_t olen; + int err, tryCount=0; + + DBG_PRINT("hci_send_req: opcode 0x%X ", opcode_le16); + + olen = sizeof(of); + if (getsockopt(_dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) { + ERR_PRINT("hci_send_req: errno %d %s", errno, strerror(errno)); + return false; + } + + filter_clear(&nf); + filter_set_ptype(HCI_EVENT_PKT, &nf); + filter_set_event(HCI_EV_CMD_STATUS, &nf); + filter_set_event(HCI_EV_CMD_COMPLETE, &nf); + filter_set_event(HCI_EV_LE_META, &nf); + if( 0 != exp_event ) { + filter_set_event(exp_event, &nf); + } + filter_set_opcode(opcode_le16, &nf); + if (setsockopt(_dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) { + ERR_PRINT("hci_send_req: errno %d %s", errno, strerror(errno)); + return false; + } + + int _timeoutMS = timeoutMS; + + if ( !send_cmd(opcode, command, command_len) ) { + ERR_PRINT("hci_send_req: errno %d %s", errno, strerror(errno)); + goto failed; + } + + // FIXME: Review the 10 packet trial for HCI responses, + // this is an artifact from the BlueZ client library. + while ( _HCI_PKT_TRY_COUNT > tryCount++ ) { + if ( _timeoutMS ) { + struct pollfd p; + int n; + + p.fd = _dd; p.events = POLLIN; + while ((n = poll(&p, 1, _timeoutMS)) < 0) { + ERR_PRINT("hci_send_req: poll: errno %d %s", errno, strerror(errno)); + if (errno == EAGAIN || errno == EINTR) { + continue; + } + goto failed; + } + + if (!n) { + DBG_PRINT("hci_send_req: poll: timeout"); + errno = ETIMEDOUT; + goto failed; + } + + _timeoutMS -= _timeoutMS/_HCI_PKT_TRY_COUNT; // reduce timeout consecutively + if ( _timeoutMS < 0 ) { + _timeoutMS = 0; + } + } + + int len; + + while ((len = read(_dd, buf, sizeof(buf))) < 0) { + ERR_PRINT("hci_send_req: read: res %d, errno %d %s", len, errno, strerror(errno)); + if (errno == EAGAIN || errno == EINTR) { + continue; + } + goto failed; + } + + const hci_event_hdr *hdr = static_cast<hci_event_hdr *>(static_cast<void *>(buf + 1)); + const uint8_t * ptr = buf + (1 + HCI_EVENT_HDR_SIZE); + len -= (1 + HCI_EVENT_HDR_SIZE); + + DBG_PRINT("hci_send_req: read: %d bytes, evt 0x%2.2X", len, hdr->evt); + + switch (hdr->evt) { + case HCI_EV_CMD_STATUS: { + const hci_ev_cmd_status *cs = static_cast<const hci_ev_cmd_status *>(static_cast<const void *>( ptr )); + + DBG_PRINT("hci_send_req: HCI_EV_CMD_STATUS: opcode 0x%X, exp_event 0x%X, status 0x%2.2X, rlen %d/%d", + cs->opcode, exp_event, cs->status, response_len, len); + + if (cs->opcode != opcode_le16) { + continue; + } + + if (exp_event != HCI_EV_CMD_STATUS) { + if (cs->status) { + errno = EIO; + goto failed; + } + break; + } + + const int rlen = MIN(len, response_len); + memcpy(response, ptr, rlen); + DBG_PRINT("hci_send_req: HCI_EV_CMD_STATUS: copied %d bytes: %s", + rlen, bytesHexString((uint8_t*)ptr, 0, rlen, false /* lsbFirst */, true /* leading0X */).c_str()); + goto done; + } + + case HCI_EV_CMD_COMPLETE: { + const hci_ev_cmd_complete *cc = static_cast<const hci_ev_cmd_complete *>(static_cast<const void *>( ptr )); + + DBG_PRINT("hci_send_req: HCI_EV_CMD_COMPLETE: opcode 0x%X (equal %d), exp_event 0x%X, r-len %d/%d", + cc->opcode, (cc->opcode == opcode_le16), exp_event, response_len, len); + + if (cc->opcode != opcode_le16) { + continue; + } + + ptr += sizeof(hci_ev_cmd_complete); + len -= sizeof(hci_ev_cmd_complete); + + const int rlen = MIN(len, response_len); + memcpy(response, ptr, rlen); + DBG_PRINT("hci_send_req: HCI_EV_CMD_COMPLETE: copied %d bytes: %s", + rlen, bytesHexString((uint8_t*)ptr, 0, rlen, false /* lsbFirst */, true /* leading0X */).c_str()); + goto done; + } + case HCI_EV_REMOTE_NAME: { + DBG_PRINT("hci_send_req: HCI_EV_REMOTE_NAME: event 0x%X (equal %d), exp_event 0x%X, r-len %d/%d", + hdr->evt, exp_event, (hdr->evt == exp_event), response_len, len); + + if (hdr->evt != exp_event) { + break; + } + + const hci_ev_remote_name *rn = static_cast<const hci_ev_remote_name *>(static_cast<const void *>( ptr )); + const hci_cp_remote_name_req *cp = static_cast<const hci_cp_remote_name_req *>(command); + + if ( rn->bdaddr != cp->bdaddr ) { + DBG_PRINT("hci_send_req: HCI_EV_REMOTE_NAME: address mismatch: cmd %s != req %s", + cp->bdaddr.toString().c_str(), rn->bdaddr.toString().c_str()); + continue; + } + + const int rlen = MIN(len, response_len); + memcpy(response, ptr, rlen); + DBG_PRINT("hci_send_req: HCI_EV_REMOTE_NAME: copied %d bytes: %s", + rlen, bytesHexString((uint8_t*)ptr, 0, rlen, false /* lsbFirst */, true /* leading0X */).c_str()); + goto done; + } + + case HCI_EV_LE_META: { + const hci_ev_le_meta *me = static_cast<const hci_ev_le_meta *>(static_cast<const void *>( ptr )); + + DBG_PRINT("hci_send_req: HCI_EV_LE_META: subevent 0x%X, exp_event 0x%X (equal %d), r-len %d/%d", + me->subevent, exp_event, (me->subevent == exp_event), response_len, len); + + if (me->subevent != exp_event) { + continue; + } + + ptr += 1; + len -= 1; + + const int rlen = MIN(len, response_len); + memcpy(response, ptr, rlen); + DBG_PRINT("hci_send_req: HCI_EV_LE_META: copied %d bytes: %s", + rlen, bytesHexString((uint8_t*)ptr, 0, rlen, false /* lsbFirst */, true /* leading0X */).c_str()); + goto done; + } + + default: { + DBG_PRINT("hci_send_req: DEFAULT: evt 0x%X, exp_event 0x%X (equal %d), r-len %d/%d", + hdr->evt, exp_event, (hdr->evt == exp_event), response_len, len); + + if (hdr->evt != exp_event) { + break; + } + + const int rlen = MIN(len, response_len); + memcpy(response, ptr, rlen); + DBG_PRINT("hci_send_req: DEFAULT: copied %d bytes: %s", + rlen, bytesHexString((uint8_t*)ptr, 0, rlen, false /* lsbFirst */, true /* leading0X */).c_str()); + goto done; + } + } + } + errno = ETIMEDOUT; + +failed: + err = errno; + setsockopt(_dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + errno = err; + return false; + +done: + setsockopt(_dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + return true; +} + +bool HCIComm::le_disconnect(const uint8_t reason) +{ + if( 0 > _dd ) { + return true; + } + if( 0 >= le_conn_handle ) { + return true; + } + hci_ev_disconn_complete rp; + hci_cp_disconnect cp; + + bzero(&cp, sizeof(cp)); + cp.handle = le_conn_handle; + cp.reason = reason; + + le_conn_handle = 0; + + if( !send_req( hci_opcode_pack(OGF_LINK_CTL, HCI_OP_DISCONNECT), &cp, sizeof(cp), + HCI_EV_DISCONN_COMPLETE, &rp, sizeof(rp) ) ) + { + DBG_PRINT("hci_le_disconnect: errno %d %s", errno, strerror(errno)); + return false; + } + + if (rp.status) { + errno = EIO; + DBG_PRINT("hci_le_disconnect: error status 0x%2.2X, errno %d %s", rp.status, errno, strerror(errno)); + return false; + } + return true; +} + +bool HCIComm::le_set_scan_enable(const uint8_t enable, const uint8_t filter_dup) { + if( 0 > _dd ) { + ERR_PRINT("hci_le_set_scan_enable: device not open"); + return false; + } + hci_cp_le_set_scan_enable cp; + uint8_t status; + + bzero(&cp, sizeof(cp)); + cp.enable = enable; + cp.filter_dup = filter_dup; + + if( !send_req( hci_opcode_pack(OGF_LE_CTL, HCI_OP_LE_SET_SCAN_ENABLE), &cp, sizeof(cp), + 0, &status, sizeof(status) ) ) + { + ERR_PRINT("hci_le_set_scan_enable: errno %d %s", errno, strerror(errno)); + return false; + } + + if (status) { + errno = EIO; + ERR_PRINT("hci_le_set_scan_enable: error status 0x%2.2X, errno %d %s", status, errno, strerror(errno)); + return false; + } + return true; +} + +bool HCIComm::le_set_scan_parameters(const uint8_t type, const uint16_t interval, + const uint16_t window, const uint8_t own_type, + const uint8_t filter) +{ + if( 0 > _dd ) { + ERR_PRINT("hci_le_set_scan_parameters: device not open"); + return false; + } + hci_cp_le_set_scan_param cp; + uint8_t status; + + bzero(&cp, sizeof(cp)); + cp.type = type; + cp.interval = cpu_to_le(interval); + cp.window = cpu_to_le(window); + cp.own_address_type = own_type; + cp.filter_policy = filter; + + if( !send_req( hci_opcode_pack(OGF_LE_CTL, HCI_OP_LE_SET_SCAN_PARAM), &cp, sizeof(cp), + 0, &status, sizeof(status) ) ) + { + ERR_PRINT("hci_le_set_scan_parameters: errno %d %s", errno, strerror(errno)); + return false; + } + + if (status) { + errno = EIO; + ERR_PRINT("hci_le_set_scan_parameters: error status 0x%2.2X, errno %d %s", status, errno, strerror(errno)); + return false; + } + + return true; +} + +void HCIComm::le_disable_scan() { + if( 0 > _dd ) { + return; + } + if( !le_scanning ) { + return; + } + const uint8_t filter_dup = 0x01; + + if( !le_set_scan_enable(0x00, filter_dup) ) { + perror("Stop scan failed"); + } else { + le_scanning = false; + } +} + +bool HCIComm::le_enable_scan(const uint8_t own_type, + const uint16_t interval, const uint16_t window) { + if( 0 > _dd ) { + DBG_PRINT("hci_le_enable_scan: device not open"); + return false; + } + if( le_scanning ) { + DBG_PRINT("hci_le_enable_scan: device already le-scanning"); + return false; + } + const uint8_t scan_type = 0x01; + // const uint8_t filter_type = 0; + const uint8_t filter_policy = 0x00; + const uint8_t filter_dup = 0x01; + + bool ok = le_set_scan_parameters(scan_type, interval, window, + own_type, filter_policy); + if ( !ok ) { + perror("Set scan parameters failed"); + return false; + } + + ok = le_set_scan_enable(0x01, filter_dup); + if ( !ok ) { + perror("Start scan failed"); + return false; + } + le_scanning = true; + return true; +} + +uint16_t HCIComm::le_create_conn(const EUI48 &peer_bdaddr, + const uint8_t peer_mac_type, + const uint8_t own_mac_type, + const uint16_t interval, const uint16_t window, + const uint16_t min_interval, const uint16_t max_interval, + const uint16_t latency, const uint16_t supervision_timeout, + const uint16_t min_ce_length, const uint16_t max_ce_length, + const uint8_t initiator_filter) +{ + if( 0 > _dd ) { + ERR_PRINT("hci_le_create_conn: device not open"); + return 0; + } + if( 0 < le_conn_handle ) { + DBG_PRINT("hci_le_create_conn: already connected 0x%X", le_conn_handle); + return le_conn_handle; + } + hci_cp_le_create_conn cp; + hci_ev_le_conn_complete rp; + + bzero(&cp, sizeof(cp)); + cp.scan_interval = cpu_to_le(interval); + cp.scan_window = cpu_to_le(window); + cp.filter_policy = initiator_filter; + cp.peer_addr_type = peer_mac_type; + cp.peer_addr = peer_bdaddr; + cp.own_address_type = own_mac_type; + cp.conn_interval_min = cpu_to_le(min_interval); + cp.conn_interval_max = cpu_to_le(max_interval); + cp.conn_latency = cpu_to_le(latency); + cp.supervision_timeout = cpu_to_le(supervision_timeout); + cp.min_ce_len = cpu_to_le(min_ce_length); + cp.max_ce_len = cpu_to_le(max_ce_length); + + if( !send_req( hci_opcode_pack(OGF_LE_CTL, HCI_OP_LE_CREATE_CONN), &cp, sizeof(cp), + HCI_EV_LE_CONN_COMPLETE, &rp, sizeof(rp) ) ) + { + ERR_PRINT("hci_le_create_conn: errno %d %s", errno, strerror(errno)); + return 0; + } + + if (rp.status) { + errno = EIO; + ERR_PRINT("hci_le_create_conn: error status 0x%2.2X, errno %d %s", rp.status, errno, strerror(errno)); + return 0; + } + + le_conn_handle = rp.handle; + + return le_conn_handle; +} + +} /* namespace direct_bt */ diff --git a/src/direct_bt/HCIDevice.cpp b/src/direct_bt/HCIDevice.cpp new file mode 100644 index 00000000..6abd854a --- /dev/null +++ b/src/direct_bt/HCIDevice.cpp @@ -0,0 +1,226 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "HCIComm.hpp" +#include "HCITypes.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +HCIDevice::HCIDevice(HCIAdapter const & a, EInfoReport const & r) +: adapter(a), ts_creation(r.getTimestamp()), mac(r.getAddress()) +{ + if( !r.isSet(EInfoReport::Element::BDADDR) ) { + throw IllegalArgumentException("HCIDevice ctor: Address not set: "+r.toString(), E_FILE_LINE); + } + update(r); +} + +HCIDevice::~HCIDevice() { + services.clear(); + msd = nullptr; +} + +std::shared_ptr<HCIDevice> HCIDevice::getSharedInstance() const { + const std::shared_ptr<HCIDevice> myself = adapter.findDiscoveredDevice(mac); + if( nullptr == myself ) { + throw InternalError("HCIDevice: Not present in HCIAdapter: "+toString(), E_FILE_LINE); + } + return myself; +} + +void HCIDevice::addService(std::shared_ptr<uuid_t> const &uuid) +{ + if( 0 > findService(uuid) ) { + services.push_back(uuid); + } +} +void HCIDevice::addServices(std::vector<std::shared_ptr<uuid_t>> const & services) +{ + for(size_t j=0; j<services.size(); j++) { + const std::shared_ptr<uuid_t> uuid = services.at(j); + addService(uuid); + } +} + +int HCIDevice::findService(std::shared_ptr<uuid_t> const &uuid) const +{ + auto begin = services.begin(); + auto it = std::find_if(begin, services.end(), [&](std::shared_ptr<uuid_t> const& p) { + return *p == *uuid; + }); + if ( it == std::end(services) ) { + return -1; + } else { + return std::distance(begin, it); + } +} + +std::string HCIDevice::toString() const { + const uint64_t t0 = getCurrentMilliseconds(); + std::string msdstr = nullptr != msd ? msd->toString() : "MSD[null]"; + std::string out("Device["+getAddressString()+", '"+getName()+ + "', age "+std::to_string(t0-ts_creation)+" ms, lup "+std::to_string(t0-ts_update)+" ms, rssi "+std::to_string(getRSSI())+ + ", tx-power "+std::to_string(tx_power)+", "+msdstr+"]"); + if(services.size() > 0 ) { + out.append("\n"); + int i=0; + for(auto it = services.begin(); it != services.end(); it++, i++) { + if( 0 < i ) { + out.append("\n"); + } + std::shared_ptr<uuid_t> p = *it; + out.append(" ").append(p->toUUID128String()).append(", ").append(std::to_string(static_cast<int>(p->getTypeSize()))).append(" bytes"); + } + } + return out; +} + +void HCIDevice::update(EInfoReport const & data) { + ts_update = data.getTimestamp(); + if( data.isSet(EInfoReport::Element::NAME) ) { + if( !name.length() || data.getName().length() > name.length() ) { + name = data.getName(); + } + } + if( data.isSet(EInfoReport::Element::NAME_SHORT) ) { + if( !name.length() ) { + name = data.getShortName(); + } + } + if( data.isSet(EInfoReport::Element::RSSI) ) { + rssi = data.getRSSI(); + } + if( data.isSet(EInfoReport::Element::TX_POWER) ) { + tx_power = data.getTxPower(); + } + if( data.isSet(EInfoReport::Element::MANUF_DATA) ) { + msd = data.getManufactureSpecificData(); + } + addServices(data.getServices()); +} + +uint16_t HCIDevice::le_connect(HCISession &session, + uint8_t peer_mac_type, uint8_t own_mac_type, + uint16_t interval, uint16_t window, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supervision_timeout, + uint16_t min_ce_length, uint16_t max_ce_length, + uint8_t initiator_filter ) +{ + if( !session.isOpen() ) { + fprintf(stderr, "Session not open\n"); + return 0; + } + const uint16_t handle = session.hciComm.le_create_conn( + mac, peer_mac_type, own_mac_type, + interval, window, min_interval, max_interval, latency, supervision_timeout, min_ce_length, max_ce_length, initiator_filter); + + if (handle <= 0) { + perror("Could not create connection"); + return 0; + } + session.connected(getSharedInstance()); + + return handle; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +/** + ServicesResolvedNotification + D-Bus BlueZ + + src/device.c: + gatt_client_init + gatt_client_ready_cb + device_svc_resolved() + device_set_svc_refreshed() + register_gatt_services() + device_svc_resolved() + + src/shared/gatt-client.c + bt_gatt_client_new() + gatt_client_init(.., uint16_t mtu) + discovery_op_create(client, 0x0001, 0xffff, init_complete, NULL); + + Setup MTU: BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546: 4.3.1 Exchange MTU + + bt_gatt_discover_all_primary_services(...) + + discover_all + bt_gatt_discover_all_primary_services(...) + + src/shared/gatt-helpers.c + bt_gatt_discover_all_primary_services + bt_gatt_discover_primary_services + discover_services + + bt_gatt_discover_secondary_services + discover_services + + discover_services + shared/gatt-helpers.c line 831: discover all primary service! + Protocol Data Unit – PDU (2-257 octets) + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->start_handle = start; + op->end_handle = end; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + // set service uuid to primary or secondary + op->service_type = primary ? GATT_PRIM_SVC_UUID : GATT_SND_SVC_UUID; + + uint8_t pdu[6]; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + put_le16(op->service_type, pdu + 4); + + op->id = bt_att_send(att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, + pdu, sizeof(pdu), + read_by_grp_type_cb, + bt_gatt_request_ref(op), + async_req_unref); + + + + */ +void ServicesResolvedNotification() { +} diff --git a/src/direct_bt/L2CAPComm.cpp b/src/direct_bt/L2CAPComm.cpp new file mode 100644 index 00000000..1bae1673 --- /dev/null +++ b/src/direct_bt/L2CAPComm.cpp @@ -0,0 +1,244 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#include "BTIoctl.hpp" +#include "HCIIoctl.hpp" +#include "L2CAPIoctl.hpp" + +#include "HCIComm.hpp" +#include "L2CAPComm.hpp" + +extern "C" { + #include <unistd.h> + #include <sys/socket.h> + #include <poll.h> +} + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +int L2CAPComm::l2cap_open_dev(const EUI48 & adapterAddress, const uint16_t psm, const uint16_t cid, const bool pubaddrAdapter, const bool blocking) { + struct sockaddr_l2 a; + int dd, err; + + // Create a loose L2CAP socket + dd = ::socket(AF_BLUETOOTH, // AF_BLUETOOTH == PF_BLUETOOTH + blocking ? SOCK_SEQPACKET : SOCK_SEQPACKET | SOCK_NONBLOCK, + BTPROTO_L2CAP); + + if( 0 > dd ) { + perror("L2CAPComm::l2cap_open_dev: socket failed"); + return dd; + } + + // Bind socket to the L2CAP adapter + // BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ + bzero(&a, sizeof(a)); + a.l2_family=AF_BLUETOOTH; + a.l2_psm = htobs(psm); + a.l2_bdaddr = adapterAddress; + a.l2_cid = htobs(cid); + a.l2_bdaddr_type = pubaddrAdapter ? L2CAPADDR_LE_PUBLIC : L2CAPADDR_LE_RANDOM; + if ( bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0 ) { + perror("L2CAPComm::l2cap_open_dev: bind failed"); + goto failed; + } + + return dd; + +failed: + err = errno; + close(dd); + errno = err; + + return -1; +} + +int L2CAPComm::l2cap_close_dev(int dd) +{ + return close(dd); +} + + +// ************************************************* +// ************************************************* +// ************************************************* + +#define STATE_ENUM(X) \ + X(Error) \ + X(Disconnected) \ + X(Connecting) \ + X(Connected) + +#define CASE_TO_STRING(V) case V: return #V; + +std::string L2CAPComm::getStateString(const State state) { + switch(state) { + STATE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown State"; +} + +L2CAPComm::State L2CAPComm::connect() { + /** BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ */ + + struct sockaddr_l2 req; + int err, res; + + if( 0 <= _dd ) { + return state; // already open + } + + _dd = l2cap_open_dev(device->getAdapter().getAddress(), psm, cid, true /* pubaddrAdapter */, blocking); + if( 0 > _dd ) { + goto failure; // open failed + } + + // actual request to connect to remote device + bzero(&req, sizeof(req)); + req.l2_family = AF_BLUETOOTH; + req.l2_psm = htobs(psm); + req.l2_bdaddr = device->getAddress(); + req.l2_cid = htobs(cid); + req.l2_bdaddr_type = pubaddr ? L2CAPADDR_LE_PUBLIC : L2CAPADDR_LE_RANDOM; + + // may block if O_NONBLOCK has not been specified in open_dev(..) + res = ::connect(_dd, (struct sockaddr*)&req, sizeof(req)); + + if( !res ) + { + state = Connected; + + } else if( EINPROGRESS == errno ) { + // non-blocking connection in progress (O_NONBLOCK), check via select / poll later + state = Connecting; + + } else { + // EALREADY == errno || ENETUNREACH == errno || EHOSTUNREACH == errno || .. + perror("L2CAPComm::connect: connect failed"); + goto failure; + } + + return state; + +failure: + err = errno; + disconnect(); + errno = err; + state = Error; + return state; +} + +bool L2CAPComm::disconnect() { + if( 0 > _dd ) { + return false; + } + interruptReadFlag = true; + l2cap_close_dev(_dd); + _dd = -1; + state = Disconnected; + return true; +} + +int L2CAPComm::read(uint8_t* buffer, const int capacity, const int timeoutMS) { + int len = 0; + if( 0 > _dd || 0 > capacity ) { + goto errout; + } + if( 0 == capacity ) { + goto done; + } + interruptReadFlag = false; + + if( timeoutMS ) { + struct pollfd p; + int n; + + p.fd = _dd; p.events = POLLIN; + while ( !interruptReadFlag && (n = poll(&p, 1, timeoutMS)) < 0 ) { + if ( !interruptReadFlag && ( errno == EAGAIN || errno == EINTR ) ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + if (!n) { + errno = ETIMEDOUT; + goto errout; + } + } + + while ((len = ::read(_dd, buffer, capacity)) < 0) { + if (errno == EAGAIN || errno == EINTR ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + +done: + return len; + +errout: + state = Error; + return -1; + +} + +int L2CAPComm::write(const uint8_t * buffer, const int length) { + int len = 0; + if( 0 > _dd || 0 > length ) { + goto errout; + } + if( 0 == length ) { + goto done; + } + + while( ( len = ::write(_dd, buffer, length) ) < 0 ) { + if( EAGAIN == errno || EINTR == errno ) { + continue; + } + goto errout; + } + +done: + return len; + +errout: + state = Error; + return -1; +} + diff --git a/src/direct_bt/MgmtComm.cpp b/src/direct_bt/MgmtComm.cpp new file mode 100644 index 00000000..6c1b9dbc --- /dev/null +++ b/src/direct_bt/MgmtComm.cpp @@ -0,0 +1,554 @@ +/* + * 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 <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cstdio> + +#include <algorithm> + +#define PERF_PRINT_ON 1 +// #define VERBOSE_ON 1 + +#include "BTIoctl.hpp" + +#include "MgmtComm.hpp" +#include "HCIIoctl.hpp" +#include "HCIComm.hpp" +#include "HCITypes.hpp" + +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <poll.h> +} + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +// ************************************************* +// ************************************************* +// ************************************************* + +#define CASE_TO_STRING(V) case V: return #V; + +#define STATUS_ENUM(X) \ + X(SUCCESS) \ + X(UNKNOWN_COMMAND) \ + X(NOT_CONNECTED) \ + X(FAILED) \ + X(CONNECT_FAILED) \ + X(AUTH_FAILED) \ + X(NOT_PAIRED) \ + X(NO_RESOURCES) \ + X(TIMEOUT) \ + X(ALREADY_CONNECTED) \ + X(BUSY) \ + X(REJECTED) \ + X(NOT_SUPPORTED) \ + X(INVALID_PARAMS) \ + X(DISCONNECTED) \ + X(NOT_POWERED) \ + X(CANCELLED) \ + X(INVALID_INDEX) \ + X(RFKILLED) \ + X(ALREADY_PAIRED) \ + X(PERMISSION_DENIED) + +std::string direct_bt::mgmt_get_status_string(const MgmtStatus opc) { + switch(opc) { + STATUS_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown Status"; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +#define OPERATION_ENUM(X) \ + X(READ_VERSION) \ + X(READ_COMMANDS) \ + X(READ_INDEX_LIST) \ + X(READ_INFO) \ + X(SET_POWERED) \ + X(SET_DISCOVERABLE) \ + X(SET_CONNECTABLE) \ + X(SET_FAST_CONNECTABLE) \ + X(SET_BONDABLE) \ + X(SET_LINK_SECURITY) \ + X(SET_SSP) \ + X(SET_HS) \ + X(SET_LE) \ + X(SET_DEV_CLASS) \ + X(SET_LOCAL_NAME) \ + X(ADD_UUID) \ + X(REMOVE_UUID) \ + X(LOAD_LINK_KEYS) \ + X(LOAD_LONG_TERM_KEYS) \ + X(DISCONNECT) \ + X(GET_CONNECTIONS) \ + X(PIN_CODE_REPLY) \ + X(PIN_CODE_NEG_REPLY) \ + X(SET_IO_CAPABILITY) \ + X(PAIR_DEVICE) \ + X(CANCEL_PAIR_DEVICE) \ + X(UNPAIR_DEVICE) \ + X(USER_CONFIRM_REPLY) \ + X(USER_CONFIRM_NEG_REPLY) \ + X(USER_PASSKEY_REPLY) \ + X(USER_PASSKEY_NEG_REPLY) \ + X(READ_LOCAL_OOB_DATA) \ + X(ADD_REMOTE_OOB_DATA) \ + X(REMOVE_REMOTE_OOB_DATA) \ + X(START_DISCOVERY) \ + X(STOP_DISCOVERY) \ + X(CONFIRM_NAME) \ + X(BLOCK_DEVICE) \ + X(UNBLOCK_DEVICE) \ + X(SET_DEVICE_ID) \ + X(SET_ADVERTISING) \ + X(SET_BREDR) \ + X(SET_STATIC_ADDRESS) \ + X(SET_SCAN_PARAMS) \ + X(SET_SECURE_CONN) \ + X(SET_DEBUG_KEYS) \ + X(SET_PRIVACY) \ + X(LOAD_IRKS) \ + X(GET_CONN_INFO) \ + X(GET_CLOCK_INFO) \ + X(ADD_DEVICE) \ + X(REMOVE_DEVICE) \ + X(LOAD_CONN_PARAM) \ + X(READ_UNCONF_INDEX_LIST) \ + X(READ_CONFIG_INFO) \ + X(SET_EXTERNAL_CONFIG) \ + X(SET_PUBLIC_ADDRESS) \ + X(START_SERVICE_DISCOVERY) \ + X(READ_LOCAL_OOB_EXT_DATA) \ + X(READ_EXT_INDEX_LIST) \ + X(READ_ADV_FEATURES) \ + X(ADD_ADVERTISING) \ + X(REMOVE_ADVERTISING) \ + X(GET_ADV_SIZE_INFO) \ + X(START_LIMITED_DISCOVERY) \ + X(READ_EXT_INFO) \ + X(SET_APPEARANCE) \ + X(GET_PHY_CONFIGURATION) \ + X(SET_PHY_CONFIGURATION) \ + X(SET_BLOCKED_KEYS) + +std::string direct_bt::mgmt_get_operation_string(const MgmtOperation op) { + switch(op) { + OPERATION_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown Operation"; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +#define OPCODE_ENUM(X) \ + X(CMD_COMPLETE) \ + X(CMD_STATUS) \ + X(CONTROLLER_ERROR) \ + X(INDEX_ADDED) \ + X(INDEX_REMOVED) \ + X(NEW_SETTINGS) \ + X(CLASS_OF_DEV_CHANGED) \ + X(LOCAL_NAME_CHANGED) \ + X(NEW_LINK_KEY) \ + X(NEW_LONG_TERM_KEY) \ + X(DEVICE_CONNECTED) \ + X(DEVICE_DISCONNECTED) \ + X(CONNECT_FAILED) \ + X(PIN_CODE_REQUEST) \ + X(USER_CONFIRM_REQUEST) \ + X(USER_PASSKEY_REQUEST) \ + X(AUTH_FAILED) \ + X(DEVICE_FOUND) \ + X(DISCOVERING) \ + X(DEVICE_BLOCKED) \ + X(DEVICE_UNBLOCKED) \ + X(DEVICE_UNPAIRED) \ + X(PASSKEY_NOTIFY) \ + X(NEW_IRK) \ + X(NEW_CSRK) \ + X(DEVICE_ADDED) \ + X(DEVICE_REMOVED) \ + X(NEW_CONN_PARAM) \ + X(UNCONF_INDEX_ADDED) \ + X(UNCONF_INDEX_REMOVED) \ + X(NEW_CONFIG_OPTIONS) \ + X(EXT_INDEX_ADDED) \ + X(EXT_INDEX_REMOVED) \ + X(LOCAL_OOB_DATA_UPDATED) \ + X(ADVERTISING_ADDED) \ + X(ADVERTISING_REMOVED) \ + X(EXT_INFO_CHANGED) \ + X(PHY_CONFIGURATION_CHANGED) + +std::string MgmtEvent::getOpcodeString(const Opcode opc) { + switch(opc) { + OPCODE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown Opcode"; +} + +MgmtEvent* MgmtEvent::getSpecialized(const uint8_t * buffer, int const buffer_size) { + const uint8_t opc = *buffer; + switch( opc ) { + case CMD_COMPLETE: + if( buffer_size >= MgmtEvtAdapterInfo::getRequiredSize() ) { + const MgmtRequest::Opcode opc = MgmtEvtCmdComplete::getReqOpcode(buffer); + if( MgmtRequest::Opcode::READ_INFO == opc ) { + return new MgmtEvtAdapterInfo(buffer, buffer_size); + } + } + return new MgmtEvtCmdComplete(buffer, buffer_size); + case CMD_STATUS: + return new MgmtEvtCmdStatus(buffer, buffer_size); + default: + return new MgmtEvent(buffer, buffer_size); + } +} + +// ************************************************* +// ************************************************* +// ************************************************* + +#undef OPCODE_ENUM +#define OPCODE_ENUM(X) \ + X(READ_VERSION) \ + X(READ_COMMANDS) \ + X(READ_INDEX_LIST) \ + X(READ_INFO) \ + X(SET_POWERED) \ + X(SET_DISCOVERABLE) \ + X(SET_CONNECTABLE) \ + X(SET_FAST_CONNECTABLE) \ + X(SET_BONDABLE) \ + X(SET_LINK_SECURITY) \ + X(SET_SSP) \ + X(SET_HS) \ + X(SET_LE) \ + X(SET_DEV_CLASS) \ + X(SET_LOCAL_NAME) + +std::string MgmtRequest::getOpcodeString(const Opcode opc) { + switch(opc) { + OPCODE_ENUM(CASE_TO_STRING) + default: ; // fall through intended + } + return "Unknown Opcode"; +} + +int MgmtRequest::read(const int dd, uint8_t* buffer, const int capacity, const int timeoutMS) { + int len = 0; + if( 0 > dd || 0 > capacity ) { + goto errout; + } + if( 0 == capacity ) { + goto done; + } + + if( timeoutMS ) { + struct pollfd p; + int n; + + p.fd = dd; p.events = POLLIN; + while ((n = poll(&p, 1, timeoutMS)) < 0) { + if (errno == EAGAIN || errno == EINTR ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + if (!n) { + errno = ETIMEDOUT; + goto errout; + } + } + + while ((len = ::read(dd, buffer, capacity)) < 0) { + if (errno == EAGAIN || errno == EINTR ) { + // cont temp unavail or interruption + continue; + } + goto errout; + } + +done: + return len; + +errout: + return -1; +} + +int MgmtRequest::write(const int dd) { + int len = 0; + if( 0 > dd || 0 > pdu.getSize() ) { + goto errout; + } + if( 0 == pdu.getSize() ) { + goto done; + } + + while( ( len = ::write(dd, pdu.get_ptr(), pdu.getSize()) ) < 0 ) { + if( EAGAIN == errno || EINTR == errno ) { + continue; + } + goto errout; + } + +done: + return len; + +errout: + return -1; +} + +int MgmtRequest::send(const int dd, uint8_t* buffer, const int capacity, const int timeoutMS) +{ + int len; + + if ( write(dd) < 0 ) { + perror("MgmtRequest::write: error"); + goto failed; + } + + if( ( len = read(dd, buffer, capacity, timeoutMS) ) < 0 ) { + perror("MgmtRequest::read: error"); + goto failed; + } + return len; + +failed: + return -1; +} + +// ************************************************* +// ************************************************* +// ************************************************* + +std::shared_ptr<MgmtEvent> MgmtHandler::send(MgmtRequest &req, uint8_t* buffer, const int capacity, const int timeoutMS) { + const std::lock_guard<std::recursive_mutex> lock(mtx); // RAII-style acquire and relinquish via destructor + + int len = req.send(comm.dd(), buffer, capacity, timeoutMS); + if( 0 >= len ) { + return nullptr; + } + const uint16_t paramSize = len >= 6 ? get_uint16(buffer, 4, true /* littleEndian */) : 0; + if( len < 6 + paramSize ) { + WARN_PRINT("mgmt_send_cmd: length mismatch %d < 6 + %d; req %s", len, paramSize, req.toString().c_str()); + return nullptr; + } + MgmtEvent * res = MgmtEvent::getSpecialized(buffer, len); + if( !res->validate(req) ) { + WARN_PRINT("mgmt_send_cmd: res mismatch: res %s; req %s", res->toString().c_str(), req.toString().c_str()); + delete res; + return nullptr; + } + return std::shared_ptr<MgmtEvent>(res); +} + +bool MgmtHandler::initAdapter(const uint16_t dev_id) { + bool ok = true; + std::shared_ptr<const AdapterInfo> adapter; + MgmtRequest req0(MgmtModeReq::READ_INFO, dev_id); + DBG_PRINT("MgmtHandler::initAdapter %d: req: %s", dev_id, req0.toString().c_str()); + { + std::shared_ptr<MgmtEvent> res = send(req0, ibuffer, ibuffer_size, HCI_TO_SEND_REQ_POLL_MS); + if( nullptr == res ) { + goto fail; + } + DBG_PRINT("MgmtHandler::initAdapter %d: res: %s", dev_id, res->toString().c_str()); + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getTotalSize() < MgmtEvtAdapterInfo::getRequiredSize()) { + ERR_PRINT("Insufficient data for adapter info: req %d, res %s", MgmtEvtAdapterInfo::getRequiredSize(), res->toString().c_str()); + goto fail; + } + adapter.reset( new AdapterInfo( *static_cast<MgmtEvtAdapterInfo*>(res.get()) ) ); + adapters.push_back(adapter); + INFO_PRINT("Adapter %d: %s", dev_id, adapter->toString().c_str()); + } + ok = setMode(dev_id, MgmtModeReq::SET_LE, 1) && ok; + ok = setMode(dev_id, MgmtModeReq::SET_POWERED, 1) && ok; + // ok = setMode(dev_id, MgmtModeReq::SET_CONNECTABLE, 1) && ok; + return true; + +fail: + return false; +} + +MgmtHandler::MgmtHandler() +:comm(HCI_DEV_NONE, HCI_CHANNEL_CONTROL) +{ + const std::lock_guard<std::recursive_mutex> lock(mtx); // RAII-style acquire and relinquish via destructor + + if( !comm.isOpen() ) { + perror("Could not open mgmt control channel"); + return; + } + PERF_TS_T0(); + + bool ok = true; + // Mandatory + { + MgmtRequest req0(MgmtModeReq::READ_VERSION, MgmtConst::INDEX_NONE); + DBG_PRINT("MgmtHandler::open req: %s", req0.toString().c_str()); + std::shared_ptr<MgmtEvent> res = send(req0, ibuffer, ibuffer_size, HCIDefaults::HCI_TO_SEND_REQ_POLL_MS); + if( nullptr == res ) { + goto fail; + } + DBG_PRINT("MgmtHandler::open res: %s", res->toString().c_str()); + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getDataSize() < 3) { + ERR_PRINT("Wrong version response: %s", res->toString().c_str()); + goto fail; + } + const uint8_t *data = res->getData(); + const uint8_t version = data[0]; + const uint16_t revision = get_uint16(data, 1, true /* littleEndian */); + INFO_PRINT("Bluetooth version %d.%d", version, revision); + if( version < 1 ) { + ERR_PRINT("Bluetooth version >= 1.0 required") + goto fail; + } + } + // Optional + { + MgmtRequest req0(MgmtModeReq::READ_COMMANDS, MgmtConst::INDEX_NONE); + DBG_PRINT("MgmtHandler::open req: %s", req0.toString().c_str()); + std::shared_ptr<MgmtEvent> res = send(req0, ibuffer, ibuffer_size, HCI_TO_SEND_REQ_POLL_MS); + if( nullptr == res ) { + goto next1; + } + DBG_PRINT("MgmtHandler::open res: %s", res->toString().c_str()); + if( MgmtEvent::Opcode::CMD_COMPLETE == res->getOpcode() && res->getDataSize() >= 4) { + const uint8_t *data = res->getData(); + const uint16_t num_commands = get_uint16(data, 0, true /* littleEndian */); + const uint16_t num_events = get_uint16(data, 2, true /* littleEndian */); + INFO_PRINT("Bluetooth %d commands, %d events", num_commands, num_events); +#ifdef VERBOSE_ON + const int expDataSize = 4 + num_commands * 2 + num_events * 2; + if( res->getDataSize() >= expDataSize ) { + for(int i=0; i< num_commands; i++) { + const MgmtOperation op = static_cast<MgmtOperation>( get_uint16(data, 4+i*2, true /* littleEndian */) ); + DBG_PRINT("kernel op %d: %s", i, mgmt_get_operation_string(op).c_str()); + } + } +#endif + } + } + +next1: + // Register to add/remove adapter optionally: + // MgmtEvent::INDEX_ADDED, MgmtConst::INDEX_NONE; + // MgmtEvent::INDEX_REMOVED, MgmtConst::INDEX_NONE; + + // Mandatory + { + MgmtRequest req0(MgmtModeReq::READ_INDEX_LIST, MgmtConst::INDEX_NONE); + DBG_PRINT("MgmtHandler::open req: %s", req0.toString().c_str()); + std::shared_ptr<MgmtEvent> res = send(req0, ibuffer, ibuffer_size, HCI_TO_SEND_REQ_POLL_MS); + if( nullptr == res ) { + goto fail; + } + DBG_PRINT("MgmtHandler::open res: %s", res->toString().c_str()); + if( MgmtEvent::Opcode::CMD_COMPLETE != res->getOpcode() || res->getDataSize() < 2) { + ERR_PRINT("Insufficient data for adapter index: res %s", res->toString().c_str()); + goto fail; + } + const uint8_t *data = res->getData(); + const uint16_t num_adapter = get_uint16(data, 0, true /* littleEndian */); + INFO_PRINT("Bluetooth %d adapter", num_adapter); + + const int expDataSize = 2 + num_adapter * 2; + if( res->getDataSize() < expDataSize ) { + ERR_PRINT("Insufficient data for %d adapter indices: res %s", num_adapter, res->toString().c_str()); + goto fail; + } + for(int i=0; ok && i < num_adapter; i++) { + const uint16_t dev_id = get_uint16(data, 2+i*2, true /* littleEndian */); + ok = initAdapter(dev_id); + } + } + if( ok ) { + PERF_TS_TD("MgmtHandler::open.ok"); + return; + } + +fail: + close(); + PERF_TS_TD("MgmtHandler::open.fail"); + return; +} + + +void MgmtHandler::close() { + const std::lock_guard<std::recursive_mutex> lock(mtx); // RAII-style acquire and relinquish via destructor + comm.close(); +} + +bool MgmtHandler::setMode(const int dev_id, const MgmtModeReq::Opcode opc, const uint8_t mode) { + const std::lock_guard<std::recursive_mutex> lock(mtx); // RAII-style acquire and relinquish via destructor + + MgmtModeReq req(opc, dev_id, mode); + DBG_PRINT("MgmtHandler::open req: %s", req.toString().c_str()); + std::shared_ptr<MgmtEvent> res = send(req, ibuffer, ibuffer_size, HCI_TO_SEND_REQ_POLL_MS); + if( nullptr != res ) { + DBG_PRINT("MgmtHandler::setMode res: %s", res->toString().c_str()); + return true; + } else { + DBG_PRINT("MgmtHandler::setMode res: NULL"); + return false; + } +} + +int MgmtHandler::findAdapterIdx(const EUI48 &mac) const { + auto begin = adapters.begin(); + auto it = std::find_if(begin, adapters.end(), [&](std::shared_ptr<const AdapterInfo> const& p) { + return p->mac == mac; + }); + if ( it == std::end(adapters) ) { + return -1; + } else { + return std::distance(begin, it); + } +} + +std::shared_ptr<const AdapterInfo> MgmtHandler::getAdapter(const int idx) const { + if( 0 > idx || idx >= static_cast<int>(adapters.size()) ) { + throw IndexOutOfBoundsException(idx, adapters.size(), 1, E_FILE_LINE); + } + return adapters.at(idx); +} diff --git a/src/direct_bt/UUID.cpp b/src/direct_bt/UUID.cpp new file mode 100644 index 00000000..4acfb2e2 --- /dev/null +++ b/src/direct_bt/UUID.cpp @@ -0,0 +1,199 @@ +/* + * 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 "UUID.hpp" + +#include "dbt_debug.hpp" + +using namespace direct_bt; + +// BASE_UUID '00000000-0000-1000-8000-00805F9B34FB' +static uint8_t bt_base_uuid_be[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; +uuid128_t direct_bt::BT_BASE_UUID( bt_base_uuid_be, 0, false ); + +uuid_t::TypeSize uuid_t::toTypeSize(const int size) { + switch(size) { + case TypeSize::UUID16_SZ: return TypeSize::UUID16_SZ; + case TypeSize::UUID32_SZ: return TypeSize::UUID32_SZ; + case TypeSize::UUID128_SZ: return TypeSize::UUID128_SZ; + } + throw IllegalArgumentException("Given size "+std::to_string(size)+", not matching uuid16_t, uuid32_t or uuid128_t", E_FILE_LINE); +} + +std::shared_ptr<const uuid_t> uuid_t::create(TypeSize t, uint8_t const * const buffer, int const byte_offset, bool littleEndian) { + if( TypeSize::UUID16_SZ == t ) { + return std::shared_ptr<const uuid_t>(new uuid16_t(buffer, byte_offset, littleEndian)); + } else if( TypeSize::UUID32_SZ == t ) { + return std::shared_ptr<const uuid_t>(new uuid32_t(buffer, byte_offset, littleEndian)); + } else if( TypeSize::UUID128_SZ == t ) { + return std::shared_ptr<const uuid_t>(new uuid128_t(buffer, byte_offset, littleEndian)); + } + throw IllegalArgumentException("Unknown Type "+std::to_string(static_cast<int>(t)), E_FILE_LINE); +} + +uuid128_t uuid_t::toUUID128(uuid128_t const & base_uuid, int const uuid32_le_octet_index) const { + switch(type) { + case TypeSize::UUID16_SZ: return uuid128_t(*((uuid16_t*)this), base_uuid, uuid32_le_octet_index); + case TypeSize::UUID32_SZ: return uuid128_t(*((uuid32_t*)this), base_uuid, uuid32_le_octet_index); + case TypeSize::UUID128_SZ: return uuid128_t(*((uuid128_t*)this)); + } + throw InternalError("Unknown Type "+std::to_string(static_cast<int>(type)), E_FILE_LINE); +} + +std::string uuid_t::toUUID128String(uuid128_t const & base_uuid, int const le_octet_index) const { + (void)base_uuid; + (void)le_octet_index; + return ""; +} + +uuid128_t::uuid128_t(uuid16_t const & uuid16, uuid128_t const & base_uuid, int const uuid16_le_octet_index) +: uuid_t(TypeSize::UUID128_SZ), value(merge_uint128(uuid16.value, base_uuid.value, uuid16_le_octet_index)) {} + +uuid128_t::uuid128_t(uuid32_t const & uuid32, uuid128_t const & base_uuid, int const uuid32_le_octet_index) +: uuid_t(TypeSize::UUID128_SZ), value(merge_uint128(uuid32.value, base_uuid.value, uuid32_le_octet_index)) {} + +std::string uuid16_t::toString() const { + const int length = 4; + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), "%.4X", value); + if( length != count ) { + throw InternalError("UUID16 string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +std::string uuid16_t::toUUID128String(uuid128_t const & base_uuid, int const le_octet_index) const +{ + uuid128_t u128(*this, base_uuid, le_octet_index); + return u128.toString(); +} + +std::string uuid32_t::toString() const { + const int length = 8; + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + + const int count = snprintf(&str[0], str.capacity(), "%.8X", value); + if( length != count ) { + throw InternalError("UUID32 string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +std::string uuid32_t::toUUID128String(uuid128_t const & base_uuid, int const le_octet_index) const +{ + uuid128_t u128(*this, base_uuid, le_octet_index); + return u128.toString(); +} + +std::string uuid128_t::toString() const { + // 87654321-0000-1000-8000-00805F9B34FB + // 0 1 2 3 4 5 + // LE: low-mem - FB349B5F0800-0080-0010-0000-12345678 - high-mem + // 5 4 3 2 1 0 + // + // BE: low-mem - 87654321-0000-1000-8000-00805F9B34FB - high-mem + // 0 1 2 3 4 5 + // + const int length = 36; + std::string str; + str.reserve(length+1); // including EOS for snprintf + str.resize(length); + uint32_t part0, part4; + uint16_t part1, part2, part3, part5; + + // snprintf uses host data type, in which values are stored, + // hence no endian conversion +#if __BYTE_ORDER == __BIG_ENDIAN + part0 = get_uint32(value.data, 0); + part1 = get_uint16(value.data, 4); + part2 = get_uint16(value.data, 6); + part3 = get_uint16(value.data, 8); + part4 = get_uint32(value.data, 10); + part5 = get_uint16(value.data, 14); +#elif __BYTE_ORDER == __LITTLE_ENDIAN + part5 = get_uint16(value.data, 0); + part4 = get_uint32(value.data, 2); + part3 = get_uint16(value.data, 6); + part2 = get_uint16(value.data, 8); + part1 = get_uint16(value.data, 10); + part0 = get_uint32(value.data, 12); +#else +#error "Unexpected __BYTE_ORDER" +#endif + const int count = snprintf(&str[0], str.capacity(), "%.8X-%.4X-%.4X-%.4X-%.8X%.4X", + part0, part1, part2, part3, part4, part5); + if( length != count ) { + throw InternalError("UUID128 string not of length "+std::to_string(length)+" but "+std::to_string(count), E_FILE_LINE); + } + return str; +} + +uuid128_t::uuid128_t(const std::string str) +: uuid_t(TypeSize::UUID128_SZ) +{ + uint32_t part0, part4; + uint16_t part1, part2, part3, part5; + + if( 36 != str.length() ) { + std::string msg("UUID128 string not of length 36 but "); + msg.append(std::to_string(str.length())); + msg.append(": "+str); + throw IllegalArgumentException(msg, E_FILE_LINE); + } + if ( sscanf(str.c_str(), "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &part0, &part1, &part2, &part3, &part4, &part5) != 6 ) + { + std::string msg("UUID128 string not in format '00000000-0000-1000-8000-00805F9B34FB' but "+str); + throw IllegalArgumentException(msg, E_FILE_LINE); + } + uint128_t value; + + // sscanf provided host data type, in which we store the values, + // hence no endian conversion +#if __BYTE_ORDER == __BIG_ENDIAN + put_uint32(value.data, 0, part0); + put_uint16(value.data, 4, part1); + put_uint16(value.data, 6, part2); + put_uint16(value.data, 8, part3); + put_uint32(value.data, 10, part4); + put_uint16(value.data, 14, part5); +#elif __BYTE_ORDER == __LITTLE_ENDIAN + put_uint16(value.data, 0, part5); + put_uint32(value.data, 2, part4); + put_uint16(value.data, 6, part3); + put_uint16(value.data, 8, part2); + put_uint16(value.data, 10, part1); + put_uint32(value.data, 12, part0); +#else +#error "Unexpected __BYTE_ORDER" +#endif +} + diff --git a/src/direct_bt/dbt_debug.hpp b/src/direct_bt/dbt_debug.hpp new file mode 100644 index 00000000..f294b0ef --- /dev/null +++ b/src/direct_bt/dbt_debug.hpp @@ -0,0 +1,63 @@ +/* + * 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 <cstring> +#include <string> +#include <cstdint> +#include <cstdio> + +extern "C" { + #include <errno.h> +} + +#ifndef DBT_DEBUG_HPP_ +#define DBT_DEBUG_HPP_ + +// #define PERF_PRINT_ON 1 +// #define VERBOSE_ON 1 + +#ifdef VERBOSE_ON + #define DBG_PRINT(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr); } +#else + #define DBG_PRINT(...) +#endif + +#ifdef PERF_PRINT_ON + #define PERF_TS_T0() const uint64_t _t0 = direct_bt::getCurrentMilliseconds() + + #define PERF_TS_TD(m) { const uint64_t _td = direct_bt::getCurrentMilliseconds() - _t0; \ + fprintf(stderr, "%s done in %d ms,\n", (m), (int)_td); } +#else + #define PERF_TS_T0() + #define PERF_TS_TD(m) +#endif + +#define ERR_PRINT(...) { fprintf(stderr, "Error @ %s:%d: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "; last errno %s\n", strerror(errno)); fflush(stderr); } + +#define WARN_PRINT(...) { fprintf(stderr, "Warning @ %s:%d: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr); } + +#define INFO_PRINT(...) { fprintf(stderr, "INFO: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr); } + +#endif /* DBT_DEBUG_HPP_ */ diff --git a/src/direct_bt/direct_bt.pc.cmake b/src/direct_bt/direct_bt.pc.cmake new file mode 100644 index 00000000..873e34ad --- /dev/null +++ b/src/direct_bt/direct_bt.pc.cmake @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib@LIB_SUFFIX@ +includedir=${prefix}/include/direct_bt + +Name: direct_bt +Description: Tiny BLE HCI library +Version: @tinyb_VERSION_STRING@ + +Libs: -L${libdir} -ltinyb +Cflags: -I${includedir} |