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 | |
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]
78 files changed, 14024 insertions, 1535 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index bfee0bbe..b2c5df40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,8 @@ set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -fno-omit-fra set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall") set (LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE PATH "Installation path for libraries") -# Set CMAKE_LIB_INSTALL_DIR if not defined +# Set CMAKE_INSTALL_XXXDIR (XXX {BIN LIB ..} if not defined +# (was: CMAKE_LIB_INSTALL_DIR) include(GNUInstallDirs) # Appends the cmake/modules path to MAKE_MODULE_PATH variable. @@ -76,6 +77,10 @@ ENDIF(DEBUG) find_path (SYSTEM_USR_DIR "stdlib.h") include_directories (${SYSTEM_USR_DIR}) +add_subdirectory (src/tinyb) + +add_subdirectory (src/direct_bt) + option (BUILDJAVA "Build Java API." OFF) IF(BUILDJAVA) @@ -113,9 +118,14 @@ if (DOXYGEN_FOUND) endif () endif (DOXYGEN_FOUND) -add_subdirectory (src/tinyb) -add_subdirectory (src/tinyb_hci) - if (BUILDEXAMPLES) add_subdirectory (examples) endif (BUILDEXAMPLES) + +if (BUILD_TESTING) + enable_testing () + add_subdirectory (test/ieee11073) + add_subdirectory (test/direct_bt) +endif(BUILD_TESTING) + + @@ -19,3 +19,32 @@ 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. + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Tinyb2's direct_bt imports certain information from Linux Kernel's BlueZ protocol stack, +allowing the use of these kernel services via system calls. +Therefore, the license has been aligned with this project. + +Related files are +- <api/direct_bt/BTIoctl.hpp>, +- <api/direct_bt/HCIIoctl.hpp> and +- <api/direct_bt/L2CAPIoctl.hpp>. + +See Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl): +<https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note> + +<quote> +NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". +</quote> + +... + +This documentation of our usage is certainly a better than GOOG's approach: +<https://android.googlesource.com/platform/system/bluetooth/+/eclair-release/bluez-clean-headers/bluetooth?autodive=0/>, +which removes all authorship and origin. + diff --git a/api/direct_bt/ATTPDUTypes.hpp b/api/direct_bt/ATTPDUTypes.hpp new file mode 100644 index 00000000..a632f4ce --- /dev/null +++ b/api/direct_bt/ATTPDUTypes.hpp @@ -0,0 +1,1369 @@ +/* + * 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. + */ + +#ifndef ATT_PDU_TYPES_HPP_ +#define ATT_PDU_TYPES_HPP_ + +/** + * Handles the Attribute Protocol (ATT) using Protocol Data Unit (PDU) + * encoded messages over L2CAP channel. + * <p> + * Implementation uses persistent memory w/ ownership + * copying PDU data to allow intermediate pipe processing. + * </p> + * <p> + * Vol 3, Part F 2 - Protocol Overview pp + * + * One attribute := { UUID type; uint16_t handle; permissions for higher layer; }, + * where + * + * UUID is an official assigned number, + * + * handle uniquely references an attribute on a server for client R/W access + * - see Vol 3, Part F 3.4.4 - 3.4.6, also 3.4.7 (notified/indicated), + * 3.4.3 (discovery) and 3.2.5 (permissions). + * + * Client sends ATT requests to a server, which shall respond to all. + * + * A device can take client and server roles concurrently. + * + * One server per device, ATT handle is unique for all supported bearers. + * For each client, server has one set of ATTs. + * The server (and hence device) can support multiple clients. + * + * Services are distinguished by range of handles for each service. + * Discovery is of these handle ranges is defined by a higher layer spec. + * + * ATT Protocol has notification and indication capabilities for efficient + * ATT value promotion to client w/o reading them (Vol 3, Part F 3.3). + * + * All ATT Protocol requests sent over an ATT bearer. + * Multiple ATT bearers can be established between two devices. + * Each ATT bearer uses a separate L2CAP channel an can have different configurations. + * + * For LE a single ATT bearer using a fixed L2CAP channel is available ASAP after + * ACL connection is established. + * Additional ATT bearers can be established using L2CAP (Vol 3, Part F 3.2.11). + * </p> + * <p> + * Vol 3, Part F 3 - Basics and Types + * + * ATT handle is uint16_t and valid if > 0x0000, max is 0xffff. + * ATT handle is unique per server. + * + * ATT value (Vol 3, Part F 3.2.4) + * + * - ATT value is uint8_t array of fixed or variable length. + * + * - ATT values might be too large for a single PDU, + * hence it must be sent using multiple PDUs. + * + * - ATT value encoding is defined by the ATT type (UUID). + * + * - ATT value transmission done via request, response, + * notification or indication + * + * - ATT value variable length is implicit by PDU carrying packet (PDU parent), + * implying: + * - One ATT value per ATT request... unless ATT values have fixed length. + * - Only one ATT value with variable length in a request... + * - L2CAP preserves DGRAM boundaries + * + * Some PDUs include the ATT value length, for which above limitations don't apply. + * + * Maximum length of an attribute value shall be 512 bytes (Vol 3, Part F 3.2.8), + * spread across multiple PDUs. + * </p> + * + * BT Core Spec v5.2: Vol 3, Part A: BT Logical Link Control and Adaption Protocol (L2CAP) + * + * BT Core Spec v5.2: Vol 3, Part F Attribute Protocol (ATT) + * BT Core Spec v5.2: Vol 3, Part F 3 ATT PDUs (Protocol Data Unit) + * BT Core Spec v5.2: Vol 3, Part F 3.3 ATT PDUs + * BT Core Spec v5.2: Vol 3, Part F 4 Security Considerations + */ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> + +#include "UUID.hpp" +#include "BTTypes.hpp" + +#include "OctetTypes.hpp" + +namespace direct_bt { + + class AttException : public RuntimeException { + protected: + AttException(std::string const type, std::string const m, const char* file, int line) noexcept + : RuntimeException(type, m, file, line) {} + + public: + AttException(std::string const m, const char* file, int line) noexcept + : RuntimeException("AttException", m, file, line) {} + }; + + class AttOpcodeException : public AttException { + public: + AttOpcodeException(std::string const m, const char* file, int line) noexcept + : AttException("AttOpcodeException", m, file, line) {} + }; + + class AttValueException : public AttException { + public: + AttValueException(std::string const m, const char* file, int line) noexcept + : AttException("AttValueException", m, file, line) {} + }; + + /** + * Attribute Protocol (ATT)'s Protocol Data Unit (PDU) message + * Vol 3, Part F 3.3 and Vol 3, Part F 3.4 + * <p> + * Little endian, however, ATT value endianess is defined by above layer. + * </p> + * <p> + * ATT_MTU Vol 3, Part F 3.2.8 + * Maximum size of any packet sent. Higher layer spec defines the default ATT_MTU value. + * LE L2CAP GATT ATT_MTU is 23 bytes (Vol 3, Part G 5.2.1). + * + * Maximum length of an attribute value shall be 512 bytes (Vol 3, Part F 3.2.8), + * spread across multiple PDUs. + * </p> + * <p> + * ATT PDU Format Vol 3, Part F 3.3.1 + * { uint8_t opcode, uint8_t param[0..ATT_MTU-X], uint8_t auth_sig[0||12] }, with + * opcode bits{ 0-5 method, 6 command-flag, 7 auth-sig-flag } and + * X = 1 if auth-sig flag of ATT-opcode is 0, or + * X = 13 if auth-sig flag of ATT-opcode is 1. + * </p> + */ + class AttPDUMsg + { + public: + /** ATT Opcode Summary Vol 3, Part F 3.4.8 */ + enum Opcode : uint8_t { + ATT_PDU_UNDEFINED = 0x00, // our own pseudo opcode, indicating no ATT PDU message + + ATT_METHOD_MASK = 0x3F, // bits 0 .. 5 + ATT_COMMAND_FLAG = 0x40, // bit 6 (counting from 0) + ATT_AUTH_SIGNATURE_FLAG = 0x80, // bit 7 (counting from 0) + + ATT_ERROR_RSP = 0x01, + ATT_EXCHANGE_MTU_REQ = 0x02, + ATT_EXCHANGE_MTU_RSP = 0x03, + ATT_FIND_INFORMATION_REQ = 0x04, + ATT_FIND_INFORMATION_RSP = 0x05, + ATT_FIND_BY_TYPE_VALUE_REQ = 0x06, + ATT_FIND_BY_TYPE_VALUE_RSP = 0x07, + ATT_READ_BY_TYPE_REQ = 0x08, + ATT_READ_BY_TYPE_RSP = 0x09, + ATT_READ_REQ = 0x0A, + ATT_READ_RSP = 0x0B, + ATT_READ_BLOB_REQ = 0x0C, + ATT_READ_BLOB_RSP = 0x0D, + ATT_READ_MULTIPLE_REQ = 0x0E, + ATT_READ_MULTIPLE_RSP = 0x0F, + ATT_READ_BY_GROUP_TYPE_REQ = 0x10, + ATT_READ_BY_GROUP_TYPE_RSP = 0x11, + ATT_WRITE_REQ = 0x12, + ATT_WRITE_RSP = 0x13, + ATT_WRITE_CMD = ATT_WRITE_REQ + ATT_COMMAND_FLAG, // = 0x52 + ATT_PREPARE_WRITE_REQ = 0x16, + ATT_PREPARE_WRITE_RSP = 0x17, + ATT_EXECUTE_WRITE_REQ = 0x18, + ATT_EXECUTE_WRITE_RSP = 0x19, + + ATT_READ_MULTIPLE_VARIABLE_REQ = 0x20, + ATT_READ_MULTIPLE_VARIABLE_RSP = 0x21, + ATT_MULTIPLE_HANDLE_VALUE_NTF = 0x23, + + ATT_HANDLE_VALUE_NTF = 0x1B, + ATT_HANDLE_VALUE_IND = 0x1D, + ATT_HANDLE_VALUE_CFM = 0x1E, + + ATT_SIGNED_WRITE_CMD = ATT_WRITE_REQ + ATT_COMMAND_FLAG + ATT_AUTH_SIGNATURE_FLAG // = 0xD2 + }; + + static std::string getOpcodeString(const Opcode opc); + + protected: + void checkOpcode(const Opcode expected) const + { + const Opcode has = getOpcode(); + if( expected != has ) { + throw AttOpcodeException("Has opcode "+uint8HexString(has, true)+" "+getOpcodeString(has)+ + ", but expected "+uint8HexString(expected, true)+" "+getOpcodeString(expected), E_FILE_LINE); + } + } + void checkOpcode(const Opcode exp1, const Opcode exp2) const + { + const Opcode has = getOpcode(); + if( exp1 != has && exp2 != has ) { + throw AttOpcodeException("Has opcode "+uint8HexString(has, true)+" "+getOpcodeString(has)+ + ", but expected either "+uint8HexString(exp1, true)+" "+getOpcodeString(exp1)+ + " or "+uint8HexString(exp1, true)+" "+getOpcodeString(exp1), E_FILE_LINE); + } + } + + virtual std::string baseString() const { + return "opcode="+uint8HexString(getOpcode(), true)+" "+getOpcodeString()+ + ", size[total="+std::to_string(pdu.getSize())+", param "+std::to_string(getPDUParamSize())+"]"; + } + virtual std::string valueString() const { + return "size "+std::to_string(getPDUValueSize())+", data " + +bytesHexString(pdu.get_ptr(), getPDUValueOffset(), getPDUValueSize(), true /* lsbFirst */, true /* leading0X */); + } + + public: + /** actual received PDU */ + POctets pdu; + + /** creation timestamp in milliseconds */ + const int64_t ts_creation; + + /** + * Return a newly created specialized instance pointer to base class. + * <p> + * Returned memory reference is managed by caller (delete etc) + * </p> + */ + static AttPDUMsg* getSpecialized(const uint8_t * buffer, int const buffer_size); + + /** Persistent memory, w/ ownership ..*/ + AttPDUMsg(const uint8_t* source, const int size) + : pdu(source, std::max(1, size)), ts_creation(getCurrentMilliseconds()) + { + pdu.check_range(0, getPDUMinSize()); + } + + /** Persistent memory, w/ ownership ..*/ + AttPDUMsg(const Opcode opc, const int size) + : pdu(std::max(1, size)), ts_creation(getCurrentMilliseconds()) + { + pdu.put_uint8(0, opc); + pdu.check_range(0, getPDUMinSize()); + } + + AttPDUMsg(const AttPDUMsg &o) noexcept = default; + AttPDUMsg(AttPDUMsg &&o) noexcept = default; + AttPDUMsg& operator=(const AttPDUMsg &o) noexcept = default; + AttPDUMsg& operator=(AttPDUMsg &&o) noexcept = default; + + virtual ~AttPDUMsg() {} + + /** ATT PDU Format Vol 3, Part F 3.3.1 */ + Opcode getOpcode() const { + return static_cast<Opcode>(pdu.get_uint8(0)); + } + std::string getOpcodeString() const { return getOpcodeString(getOpcode()); } + + /** ATT PDU Format Vol 3, Part F 3.3.1 */ + Opcode getOpMethod() const { + return static_cast<Opcode>(getOpcode() & ATT_METHOD_MASK); + } + + /** ATT PDU Format Vol 3, Part F 3.3.1 */ + bool getOpCommandFlag() const { + return static_cast<Opcode>(getOpcode() & ATT_COMMAND_FLAG); + } + + /** ATT PDU Format Vol 3, Part F 3.3.1 */ + bool getOpAuthSigFlag() const { + return static_cast<Opcode>(getOpcode() & ATT_AUTH_SIGNATURE_FLAG); + } + + /** + * ATT PDU Format Vol 3, Part F 3.3.1 + * <p> + * The ATT Authentication Signature size in octets. + * </p> + * <p> + * This auth-signature comes at the very last of the PDU. + * </p> + */ + int getAuthSigSize() const { + return getOpAuthSigFlag() ? 12 : 0; + } + + /** + * ATT PDU Format Vol 3, Part F 3.3.1 + * <p> + * The ATT PDU parameter size in octets + * less opcode (1 byte) and auth-signature (0 or 12 bytes). + * </p> + * <pre> + * param-size := pdu.size - getAuthSigSize() - 1 + * </pre> + * <p> + * Note that the PDU parameter include the PDU value below. + * </p> + * <p> + * Note that the optional auth-signature is at the end of the PDU. + * </p> + */ + int getPDUParamSize() const { + return pdu.getSize() - getAuthSigSize() - 1 /* opcode */; + } + + /** + * Returns the octet offset to the value segment in this PDU + * including the mandatory opcode, + * i.e. the number of octets until the first value octet. + * <p> + * Note that the ATT PDU value is part of the PDU param, + * where it is the last segment. + * </p> + * <p> + * The value offset is ATT PDU specific and may point + * to the variable user data post handle etc within the PDU Param block. + * </p> + * <p> + * Note that the opcode must be included in the implementation, + * as it may be used to reference the value in the pdu + * conveniently. + * </p> + */ + virtual int getPDUValueOffset() const { return 1; /* default: opcode */ } + + /** + * Returns this PDU's minimum size, i.e. + * <pre> + * opcode + param - value + auth_signature + * </pre> + * Value is excluded as it might be flexible. + */ + int getPDUMinSize() const { + return getPDUValueOffset() + getAuthSigSize(); + } + + /** + * Returns the octet size of the value attributes in this PDI, + * i.e. getPDUParamSize() - getPDUValueOffset() + 1. + * <p> + * Note that the opcode size of 1 octet is re-added as included in getPDUValueOffset() + * for convenience but already taken-off in getPDUParamSize() for spec compliance! + * </p> + * <pre> + * value-size := param-size - value-offset + 1 + * param-size := pdu.size - getAuthSigSize() - 1 + * + * value-size := pdu.size - getAuthSigSize() - 1 - value-offset + 1 + * value-size := pdu.size - getAuthSigSize() - value-offset + * </pre> + */ + int getPDUValueSize() const { return getPDUParamSize() - getPDUValueOffset() + 1; } + + /** + * Returns the theoretical maximum value size of a PDU. + * <pre> + * ATT_MTU - getAuthSigSize() - value-offset + * </pre> + */ + int getMaxPDUValueSize(const int mtu) const { + return mtu - getAuthSigSize() - getPDUValueOffset(); + } + + virtual std::string getName() const { + return "AttPDUMsg"; + } + + virtual std::string toString() const { + return getName()+"["+baseString()+", value["+valueString()+"]]"; + } + }; + + /** + * Our own pseudo opcode, indicating no ATT PDU message. + * <p> + * ATT_PDU_UNDEFINED + * </p> + */ + class AttPDUUndefined: public AttPDUMsg + { + public: + AttPDUUndefined(const uint8_t* source, const int length) : AttPDUMsg(source, length) { + checkOpcode(ATT_PDU_UNDEFINED); + } + + /** opcode */ + int getPDUValueOffset() const override { return 1; } + + std::string getName() const override { + return "AttPDUUndefined"; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.1.1 + * <p> + * ATT_ERROR_RSP (ATT Opcode 0x01) + * </p> + */ + class AttErrorRsp: public AttPDUMsg + { + public: + enum ErrorCode : uint8_t { + INVALID_HANDLE = 0x01, + NO_READ_PERM = 0x02, + NO_WRITE_PERM = 0x03, + INVALID_PDU = 0x04, + INSUFF_AUTHENTICATION = 0x05, + UNSUPPORTED_REQUEST = 0x06, + INVALID_OFFSET = 0x07, + INSUFF_AUTHORIZATION = 0x08, + PREPARE_QUEUE_FULL = 0x09, + ATTRIBUTE_NOT_FOUND = 0x0A, + ATTRIBUTE_NOT_LONG = 0x0B, + INSUFF_ENCRYPTION_KEY_SIZE = 0x0C, + INVALID_ATTRIBUTE_VALUE_LEN = 0x0D, + UNLIKELY_ERROR = 0x0E, + INSUFF_ENCRYPTION = 0x0F, + UNSUPPORTED_GROUP_TYPE = 0x10, + INSUFFICIENT_RESOURCES = 0x11, + DB_OUT_OF_SYNC = 0x12, + FORBIDDEN_VALUE = 0x13 + }; + + static std::string getPlainErrorString(const ErrorCode errorCode); + + AttErrorRsp(const uint8_t* source, const int length) : AttPDUMsg(source, length) { + checkOpcode(ATT_ERROR_RSP); + } + + /** opcode + reqOpcodeCause + handleCause + errorCode */ + int getPDUValueOffset() const override { return 1 + 1 + 2 + 1; } + + uint8_t getRequestedOpcodeCause() const { + return pdu.get_uint8(1); + } + + uint16_t getHandleCause() const { + return pdu.get_uint16(2); + } + + ErrorCode getErrorCode() const { + return static_cast<ErrorCode>(pdu.get_uint8(4)); + } + + std::string getErrorString() const { + const ErrorCode ec = getErrorCode(); + return uint8HexString(ec, true) + ": " + getPlainErrorString(ec); + } + + std::string getName() const override { + return "AttErrorRsp"; + } + + protected: + std::string valueString() const override { + return getErrorString(); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.2.2 + * <p> + * ATT_EXCHANGE_MTU_REQ, ATT_EXCHANGE_MTU_RSP + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.3.1 Exchange MTU (Server configuration) + * </p> + */ + class AttExchangeMTU: public AttPDUMsg + { + private: + uint8_t _data[1+2]; + + public: + AttExchangeMTU(const uint8_t* source, const int length) : AttPDUMsg(source, length) { + checkOpcode(ATT_EXCHANGE_MTU_RSP); + } + + AttExchangeMTU(const uint16_t mtuSize) + : AttPDUMsg(ATT_EXCHANGE_MTU_REQ, 1+2) + { + pdu.put_uint16(1, mtuSize); + } + + /** opcode + mtu-size */ + int getPDUValueOffset() const override { return 1+2; } + + uint16_t getMTUSize() const { + return pdu.get_uint16(1); + } + + std::string getName() const override { + return "AttExchangeMTU"; + } + + protected: + std::string valueString() const override { + return "mtu "+std::to_string(getMTUSize()); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.3 + * <p> + * ATT_READ_REQ + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value + * </p> + */ + class AttReadReq : public AttPDUMsg + { + public: + AttReadReq(const uint16_t handle) + : AttPDUMsg(ATT_READ_REQ, 1+2) + { + pdu.put_uint16(1, handle); + } + + /** opcode + handle */ + int getPDUValueOffset() const override { return 1+2; } + + uint16_t getHandle() const { + return pdu.get_uint16( 1 ); + } + + std::string getName() const override { + return "AttReadReq"; + } + + protected: + std::string valueString() const override { + return "handle "+uint16HexString(getHandle(), true); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.4 + * <p> + * ATT_READ_RSP (ATT Opcode 0x0B) + * </p> + * <p> + * If expected value size exceeds getValueSize(), continue with ATT_READ_BLOB_REQ (3.4.4.5), + * see shallReadBlobReq(..) + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value + * </p> + */ + class AttReadRsp: public AttPDUMsg + { + private: + const TOctetSlice view; + + public: + static bool instanceOf(); + + AttReadRsp(const uint8_t* source, const int length) + : AttPDUMsg(source, length), view(pdu, getPDUValueOffset(), getPDUValueSize()) { + checkOpcode(ATT_READ_RSP); + } + + /** opcode */ + int getPDUValueOffset() const override { return 1; } + + uint8_t const * getValuePtr() const { return pdu.get_ptr(getPDUValueOffset()); } + + TOctetSlice const & getValue() const { return view; } + + std::string getName() const override { + return "AttReadRsp"; + } + + protected: + std::string valueString() const override { + return "size "+std::to_string(getPDUValueSize())+", data "+view.toString(); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.5 + * <p> + * ATT_READ_BLOB_REQ + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value + * </p> + */ + class AttReadBlobReq : public AttPDUMsg + { + public: + AttReadBlobReq(const uint16_t handle, const uint16_t value_offset) + : AttPDUMsg(ATT_READ_BLOB_REQ, 1+2+2) + { + pdu.put_uint16(1, handle); + pdu.put_uint16(3, value_offset); + } + + /** opcode + handle + value_offset */ + int getPDUValueOffset() const override { return 1 + 2 + 2; } + + uint16_t getHandle() const { + return pdu.get_uint16( 1 ); + } + + uint16_t getValueOffset() const { + return pdu.get_uint16( 1 + 2 ); + } + + std::string getName() const override { + return "AttReadBlobReq"; + } + + protected: + std::string valueString() const override { + return "handle "+uint16HexString(getHandle(), true)+", valueOffset "+uint16HexString(getValueOffset(), true); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.6 + * <p> + * ATT_READ_BLOB_RSP + * </p> + * <p> + * If expected value size exceeds getValueSize(), continue with ATT_READ_BLOB_REQ (3.4.4.5), + * see shallReadBlobReq(..) + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value + * </p> + */ + class AttReadBlobRsp: public AttPDUMsg + { + private: + const TOctetSlice view; + + public: + static bool instanceOf(); + + AttReadBlobRsp(const uint8_t* source, const int length) + : AttPDUMsg(source, length), view(pdu, getPDUValueOffset(), getPDUValueSize()) { + checkOpcode(ATT_READ_BLOB_RSP); + } + + /** opcode */ + int getPDUValueOffset() const override { return 1; } + + uint8_t const * getValuePtr() const { return pdu.get_ptr(getPDUValueOffset()); } + + TOctetSlice const & getValue() const { return view; } + + std::string getName() const override { + return "AttReadBlobRsp"; + } + + protected: + std::string valueString() const override { + return "size "+std::to_string(getPDUValueSize())+", data "+view.toString(); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.5.1 + * <p> + * ATT_WRITE_REQ + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * </p> + */ + class AttWriteReq : public AttPDUMsg + { + private: + const TOctetSlice view; + + public: + AttWriteReq(const uint16_t handle, const TROOctets & value) + : AttPDUMsg(ATT_WRITE_REQ, 1+2+value.getSize()), view(pdu, getPDUValueOffset(), getPDUValueSize()) + { + pdu.put_uint16(1, handle); + for(int i=0; i<value.getSize(); i++) { + pdu.put_uint8(3+i, value.get_uint8(i)); + } + } + + /** opcode + handle */ + int getPDUValueOffset() const override { return 1 + 2; } + + uint16_t getHandle() const { + return pdu.get_uint16( 1 ); + } + + uint8_t const * getValuePtr() const { return pdu.get_ptr(getPDUValueOffset()); } + + TOctetSlice const & getValue() const { return view; } + + std::string getName() const override { + return "AttWriteReq"; + } + + protected: + std::string valueString() const override { + return "handle "+uint16HexString(getHandle(), true)+", data "+view.toString();; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.5.2 + * <p> + * ATT_WRITE_RSP + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value + * </p> + */ + class AttWriteRsp : public AttPDUMsg + { + public: + AttWriteRsp(const uint8_t* source, const int length) + : AttPDUMsg(source, length) { + checkOpcode(ATT_WRITE_RSP); + } + + /** opcode */ + int getPDUValueOffset() const override { return 1; } + + std::string getName() const override { + return "AttWriteRsp"; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.5.3 + * <p> + * ATT_WRITE_CMD + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value without Response + * </p> + */ + class AttWriteCmd : public AttPDUMsg + { + private: + const TOctetSlice view; + + public: + AttWriteCmd(const uint16_t handle, const TROOctets & value) + : AttPDUMsg(ATT_WRITE_CMD, 1+2+value.getSize()), view(pdu, getPDUValueOffset(), getPDUValueSize()) + { + pdu.put_uint16(1, handle); + for(int i=0; i<value.getSize(); i++) { + pdu.put_uint8(3+i, value.get_uint8(i)); + } + } + + /** opcode + handle */ + int getPDUValueOffset() const override { return 1 + 2; } + + uint16_t getHandle() const { + return pdu.get_uint16( 1 ); + } + + uint8_t const * getValuePtr() const { return pdu.get_ptr(getPDUValueOffset()); } + + TOctetSlice const & getValue() const { return view; } + + std::string getName() const override { + return "AttWriteCmd"; + } + + protected: + std::string valueString() const override { + return "handle "+uint16HexString(getHandle(), true)+", data "+view.toString();; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.7.1 and 3.4.7.2 + * <p> + * A received ATT_HANDLE_VALUE_NTF or ATT_HANDLE_VALUE_IND from server. + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.10 Characteristic Value Notification + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.11 Characteristic Value Indications + * </p> + * <p> + * Send by server to notify or indicate an ATT value (at any time). + * </p> + */ + class AttHandleValueRcv: public AttPDUMsg + { + private: + const TOctetSlice view; + + public: + AttHandleValueRcv(const uint8_t* source, const int length) + : AttPDUMsg(source, length), view(pdu, getPDUValueOffset(), getPDUValueSize()) { + checkOpcode(ATT_HANDLE_VALUE_NTF, ATT_HANDLE_VALUE_IND); + } + + /** opcode + handle */ + int getPDUValueOffset() const override { return 1+2; } + + uint16_t getHandle() const { + return pdu.get_uint16(1); + } + + uint8_t const * getValuePtr() const { return pdu.get_ptr(getPDUValueOffset()); } + + TOctetSlice const & getValue() const { return view; } + + bool isNotification() const { + return ATT_HANDLE_VALUE_NTF == getOpcode(); + } + + bool isIndication() const { + return ATT_HANDLE_VALUE_IND == getOpcode(); + } + + std::string getName() const override { + return "AttHandleValueRcv"; + } + + protected: + std::string valueString() const override { + return "handle "+uint16HexString(getHandle(), true)+", size "+std::to_string(getPDUValueSize())+", data "+view.toString(); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.7.3 + * <p> + * ATT_HANDLE_VALUE_CFM to server, acknowledging ATT_HANDLE_VALUE_IND + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.11 Characteristic Value Indications + * </p> + */ + class AttHandleValueCfm: public AttPDUMsg + { + private: + uint8_t _data[1]; + + public: + AttHandleValueCfm() + : AttPDUMsg(ATT_HANDLE_VALUE_CFM, 1) + { + } + + /** opcode */ + int getPDUValueOffset() const override { return 1; } + + std::string getName() const override { + return "AttHandleValueCfm"; + } + }; + + class AttElementList : public AttPDUMsg + { + protected: + AttElementList(const uint8_t* source, const int length) + : AttPDUMsg(source, length) {} + + virtual std::string addValueString() const { return ""; } + virtual std::string elementString(const int idx) const { return "not implemented"; } + + std::string valueString() const override { + std::string res = "size "+std::to_string(getPDUValueSize())+", "+addValueString()+"elements[count "+std::to_string(getElementCount())+", "+ + "size [total "+std::to_string(getElementTotalSize())+", value "+std::to_string(getElementValueSize())+"]: "; + const int count = getElementCount(); + for(int i=0; i<count; i++) { + res += std::to_string(i)+"["+elementString(i)+"],"; + } + res += "]"; + return res; + } + + public: + virtual ~AttElementList() {} + + virtual int getElementTotalSize() const = 0; + virtual int getElementValueSize() const = 0; + virtual int getElementCount() const = 0; + + int getElementPDUOffset(const int elementIdx) const { + return getPDUValueOffset() + elementIdx * getElementTotalSize(); + } + + uint8_t const * getElementPtr(const int elementIdx) const { + return pdu.get_ptr(getElementPDUOffset(elementIdx)); + } + + std::string getName() const override { + return "AttElementList"; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.1 + * <p> + * ATT_READ_BY_TYPE_REQ + * </p> + * + * <p> + * and + * </p> + * + * ATT Protocol PDUs Vol 3, Part F 3.4.4.9 + * <p> + * ATT_READ_BY_GROUP_TYPE_REQ + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value + * </p> + */ + class AttReadByNTypeReq : public AttPDUMsg + { + private: + uuid_t::TypeSize getUUIFormat() const { + return uuid_t::toTypeSize(this->getPDUValueSize()); + } + + public: + AttReadByNTypeReq(const bool groupTypeReq, const uint16_t startHandle, const uint16_t endHandle, const uuid_t & uuid) + : AttPDUMsg(groupTypeReq ? ATT_READ_BY_GROUP_TYPE_REQ : ATT_READ_BY_TYPE_REQ, 1+2+2+uuid.getTypeSize()) + { + if( uuid.getTypeSize() != uuid_t::TypeSize::UUID16_SZ && uuid.getTypeSize()!= uuid_t::TypeSize::UUID128_SZ ) { + throw IllegalArgumentException("Only UUID16 and UUID128 allowed: "+uuid.toString(), E_FILE_LINE); + } + pdu.put_uint16(1, startHandle); + pdu.put_uint16(3, endHandle); + pdu.put_uuid(5, uuid); + } + + /** opcode + handle-start + handle-end */ + int getPDUValueOffset() const override { return 1 + 2 + 2; } + + uint16_t getStartHandle() const { + return pdu.get_uint16( 1 ); + } + + uint16_t getEndHandle() const { + return pdu.get_uint16( 1 + 2 /* 1 handle size */ ); + } + + std::string getName() const override { + return "AttReadByNTypeReq"; + } + + std::shared_ptr<const uuid_t> getNType() const { + return pdu.get_uuid( getPDUValueOffset(), getUUIFormat() ); + } + + protected: + std::string valueString() const override { + return "handle ["+uint16HexString(getStartHandle(), true)+".."+uint16HexString(getEndHandle(), true)+ + "], uuid "+getNType()->toString(); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.2 + * <p> + * ATT_READ_BY_TYPE_RSP + * </p> + * <p> + * Contains a list of elements, each comprised of handle-value pairs. + * The handle is comprised of two octets, i.e. uint16_t. + * <pre> + * element := { uint16_t handle, uint8_t value[value-size] } + * </pre> + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value + * </p> + */ + class AttReadByTypeRsp: public AttElementList + { + public: + /** + * element := { uint16_t handle, uint8_t value[value-size] } + */ + class Element { + private: + const TOctetSlice view; + + public: + Element(const AttReadByTypeRsp & p, const int idx) + : view(p.pdu, p.getElementPDUOffset(idx), p.getElementTotalSize()) {} + + uint16_t getHandle() const { + return view.get_uint16(0); + } + + uint8_t const * getValuePtr() const { + return view.get_ptr(2 /* handle size */); + } + + int getValueSize() const { return view.getSize() - 2 /* handle size */; } + + std::string toString() const { + return "handle "+uint16HexString(getHandle(), true)+ + ", data "+bytesHexString(getValuePtr(), 0, getValueSize(), true /* lsbFirst */, true /* leading0X */); + } + }; + + AttReadByTypeRsp(const uint8_t* source, const int length) : AttElementList(source, length) { + checkOpcode(ATT_READ_BY_TYPE_RSP); + + if( getPDUValueSize() % getElementTotalSize() != 0 ) { + throw AttValueException("PDUReadByTypeRsp: Invalid packet size: pdu-value-size "+std::to_string(getPDUValueSize())+ + " not multiple of element-size "+std::to_string(getElementTotalSize()), E_FILE_LINE); + } + } + + /** opcode + element-size */ + int getPDUValueOffset() const override { return 1 + 1; } + + /** Returns size of each element, i.e. handle-value pair. */ + int getElementTotalSize() const override { + return pdu.get_uint8(1); + } + + /** + * Net element-value size, i.e. element size less handle. + * <p> + * element := { uint16_t handle, uint8_t value[value-size] } + * </p> + */ + int getElementValueSize() const override { + return getElementTotalSize() - 2; + } + + int getElementCount() const override { + return getPDUValueSize() / getElementTotalSize(); + } + + Element getElement(const int elementIdx) const { + return Element(*this, elementIdx); + } + + uint16_t getElementHandle(const int elementIdx) const { + return pdu.get_uint16( getElementPDUOffset(elementIdx) ); + } + + uint8_t * getElementValuePtr(const int elementIdx) { + return pdu.get_wptr() + getElementPDUOffset(elementIdx) + 2 /* handle size */; + } + + std::string getName() const override { + return "AttReadByTypeRsp"; + } + + protected: + std::string elementString(const int idx) const override { + return getElement(idx).toString(); + } + }; + + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.4.10 + * <p> + * ATT_READ_BY_GROUP_TYPE_RSP + * </p> + * <p> + * Contains a list of elements, each comprised of { start_handle, end_handle, value } triple. + * Both handle are each comprised of two octets, i.e. uint16_t. + * <pre> + * element := { uint16_t startHandle, uint16_t endHandle, uint8_t value[value-size] } + * </pre> + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * </p> + */ + class AttReadByGroupTypeRsp : public AttElementList + { + public: + /** + * element := { uint16_t startHandle, uint16_t endHandle, uint8_t value[value-size] } + */ + class Element { + private: + const TOctetSlice view; + + public: + Element(const AttReadByGroupTypeRsp & p, const int idx) + : view(p.pdu, p.getElementPDUOffset(idx), p.getElementTotalSize()) {} + + uint16_t getStartHandle() const { + return view.get_uint16(0); + } + + uint16_t getEndHandle() const { + return view.get_uint16(2); + } + + uint8_t const * getValuePtr() const { + return view.get_ptr(4 /* handle size */); + } + + int getValueSize() const { return view.getSize() - 4 /* handle size */; } + }; + + AttReadByGroupTypeRsp(const uint8_t* source, const int length) : AttElementList(source, length) { + checkOpcode(ATT_READ_BY_GROUP_TYPE_RSP); + + if( getPDUValueSize() % getElementTotalSize() != 0 ) { + throw AttValueException("PDUReadByGroupTypeRsp: Invalid packet size: pdu-value-size "+std::to_string(getPDUValueSize())+ + " not multiple of element-size "+std::to_string(getElementTotalSize()), E_FILE_LINE); + } + } + + /** opcode + element-size */ + int getPDUValueOffset() const override { return 1 + 1; } + + /** Returns size of each element, i.e. handle-value triple. */ + int getElementTotalSize() const override { + return pdu.get_uint8(1); + } + + /** + * Net element-value size, i.e. element size less handles. + * <p> + * element := { uint16_t startHandle, uint16_t endHandle, uint8_t value[value-size] } + * </p> + */ + int getElementValueSize() const override { + return getElementTotalSize() - 4; + } + + int getElementCount() const override { + return getPDUValueSize() / getElementTotalSize(); + } + + Element getElement(const int elementIdx) const { + return Element(*this, elementIdx); + } + + uint16_t getElementStartHandle(const int elementIdx) const { + return pdu.get_uint16( getElementPDUOffset(elementIdx) ); + } + + uint16_t getElementEndHandle(const int elementIdx) const { + return pdu.get_uint16( getElementPDUOffset(elementIdx) + 2 /* 1 handle size */ ); + } + + uint8_t * getElementValuePtr(const int elementIdx) { + return pdu.get_wptr() + getElementPDUOffset(elementIdx) + 4 /* 2 handle size */; + } + + std::string getName() const override { + return "AttReadByGroupTypeRsp"; + } + + protected: + std::string elementString(const int idx) const override { + Element e = getElement(idx); + return "handle ["+uint16HexString(e.getStartHandle(), true)+".."+uint16HexString(e.getEndHandle(), true)+ + "], data "+bytesHexString(e.getValuePtr(), 0, e.getValueSize(), true /* lsbFirst */, true /* leading0X */); + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.3.1 + * <p> + * ATT_FIND_INFORMATION_REQ + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors + * </p> + */ + class AttFindInfoReq : public AttPDUMsg + { + public: + AttFindInfoReq(const uint16_t startHandle, const uint16_t endHandle) + : AttPDUMsg(ATT_FIND_INFORMATION_REQ, 1+2+2) + { + pdu.put_uint16(1, startHandle); + pdu.put_uint16(3, endHandle); + } + + /** opcode + handle_start + handle_end */ + int getPDUValueOffset() const override { return 1 + 2 + 2; } + + uint16_t getStartHandle() const { + return pdu.get_uint16( 1 ); + } + + uint16_t getEndHandle() const { + return pdu.get_uint16( 1 + 2 ); + } + + std::string getName() const override { + return "AttFindInfoReq"; + } + + protected: + std::string valueString() const override { + return "handle ["+uint16HexString(getStartHandle(), true)+".."+uint16HexString(getEndHandle(), true)+"]"; + } + }; + + /** + * ATT Protocol PDUs Vol 3, Part F 3.4.3.2 + * <p> + * ATT_FIND_INFORMATION_RSP + * </p> + * <p> + * Contains a list of elements, each comprised of { handle, [UUID16 | UUID128] } pair. + * The handle is comprised of two octets, i.e. uint16_t. + * The UUID is either comprised of 2 octets for UUID16 or 16 octets for UUID128 + * depending on the given format. + * <pre> + * element := { uint16_t handle, UUID value }, with a UUID of UUID16 or UUID128 + * </pre> + * </p> + * Used in: + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors + * </p> + */ + class AttFindInfoRsp: public AttElementList + { + private: + uuid_t::TypeSize getUUIFormat() const { + const int f = pdu.get_uint8(1); + if( 0x01 == f ) { + return uuid_t::TypeSize::UUID16_SZ; + } else if( 0x02 == f ) { + return uuid_t::TypeSize::UUID128_SZ; + } + throw AttValueException("PDUFindInfoRsp: Invalid format "+std::to_string(f)+", not UUID16 (1) or UUID128 (2)", E_FILE_LINE); + } + + public: + /** + * element := { uint16_t handle, UUID value }, with a UUID of UUID16 or UUID128 + */ + class Element { + public: + const uint16_t handle; + const std::shared_ptr<const uuid_t> uuid; + + Element(const AttFindInfoRsp & p, const int idx) + : handle( p.getElementHandle(idx) ), uuid( p.getElementValue(idx) ) + { } + }; + + AttFindInfoRsp(const uint8_t* source, const int length) : AttElementList(source, length) { + checkOpcode(ATT_FIND_INFORMATION_RSP); + if( getPDUValueSize() % getElementTotalSize() != 0 ) { + throw AttValueException("PDUFindInfoRsp: Invalid packet size: pdu-value-size "+std::to_string(getPDUValueSize())+ + " not multiple of element-size "+std::to_string(getElementTotalSize()), E_FILE_LINE); + } + } + + /** opcode + format */ + int getPDUValueOffset() const override { return 1 + 1; } + + /** Returns size of each element, i.e. handle-value triple. */ + int getElementTotalSize() const override { + return 2 + getElementValueSize(); + } + + /** + * Net element-value size, i.e. element size less handles. + * <p> + * element := { uint16_t handle, UUID value }, with a UUID of UUID16 or UUID128 + * </p> + */ + int getElementValueSize() const override { + return getUUIFormat(); + } + + int getElementCount() const override { + return getPDUValueSize() / getElementTotalSize(); + } + + Element getElement(const int elementIdx) const { + return Element(*this, elementIdx); + } + + uint16_t getElementHandle(const int elementIdx) const { + return pdu.get_uint16( getElementPDUOffset(elementIdx) ); + } + + std::shared_ptr<const uuid_t> getElementValue(const int elementIdx) const { + return pdu.get_uuid( getElementPDUOffset(elementIdx) + 2, getUUIFormat() ); + } + + std::string getName() const override { + return "AttFindInfoRsp"; + } + + protected: + std::string addValueString() const override { return "format "+std::to_string(pdu.get_uint8(1))+", "; } + + std::string elementString(const int idx) const override { + Element e = getElement(idx); + return "handle "+uint16HexString(e.handle, true)+ + ", uuid "+e.uuid.get()->toString(); + } + }; +} + + +#endif /* ATT_PDU_TYPES_HPP_ */ diff --git a/api/direct_bt/BTAddress.hpp b/api/direct_bt/BTAddress.hpp new file mode 100644 index 00000000..4fd6807d --- /dev/null +++ b/api/direct_bt/BTAddress.hpp @@ -0,0 +1,109 @@ +/* + * 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. + */ + +#ifndef BT_ADDRESS_HPP_ +#define BT_ADDRESS_HPP_ + +#include <cstring> +#include <string> +#include <cstdint> + +/** + * A packed 48 bit EUI-48 identifier, formerly known as MAC-48 + * or simply network device MAC address (Media Access Control address). + * <p> + * Since we utilize this type within *ioctl* _and_ our high-level *types*, + * declaration is not within our *direct_bt* namespace. + * </p> + */ +struct __attribute__((packed)) EUI48 { + uint8_t b[6]; // == sizeof(EUI48) + + EUI48() { bzero(b, sizeof(EUI48)); } + EUI48(const uint8_t * b); + EUI48(const std::string mac); + EUI48(const EUI48 &o) noexcept = default; + EUI48(EUI48 &&o) noexcept = default; + EUI48& operator=(const EUI48 &o) noexcept = default; + EUI48& operator=(EUI48 &&o) noexcept = default; + + std::string toString() const; +}; + +inline bool operator<(const EUI48& lhs, const EUI48& rhs) +{ return memcmp(&lhs, &rhs, sizeof(EUI48))<0; } + +inline bool operator==(const EUI48& lhs, const EUI48& rhs) +{ return !memcmp(&lhs, &rhs, sizeof(EUI48)); } + +inline bool operator!=(const EUI48& lhs, const EUI48& rhs) +{ return !(lhs == rhs); } + +/** EUI48 MAC address matching any device, i.e. '0:0:0:0:0:0'. */ +extern const EUI48 EUI48_ANY_DEVICE; +/** EUI48 MAC address matching all device, i.e. 'ff:ff:ff:ff:ff:ff'. */ +extern const EUI48 EUI48_ALL_DEVICE; +/** EUI48 MAC address matching local device, i.e. '0:0:0:ff:ff:ff'. */ +extern const EUI48 EUI48_LOCAL_DEVICE; + +namespace direct_bt { + + /** + * BT Core Spec v5.2: Vol 3, Part C Generic Access Profile (GAP): 15.1.1.1 Public Bluetooth address + * 1) BT public address used as BD_ADDR for BR/EDR physical channel is defined in Vol 2, Part B 1.2 + * - EUI-48 or MAC (6 octets) + * + * 2) BT public address used as BD_ADDR for the LE physical channel is defined in Vol 6, Part B 1.3 + * + * BT Core Spec v5.2: Vol 3, Part C Generic Access Profile (GAP): 15.1.1.2 Random Bluetooth address + * 3) BT random address used as BD_ADDR on the LE physical channel is defined in Vol 3, Part C 10.8 + * + * +++ + * + * For HCI LE Address-Type it is: PUBLIC: 0x00, RANDOM: 0x01 + * + * BT Core Spec v5.2: Vol 4, Part E Host Controller Interface (HCI) Functionality: + * + * > 7.8.5: LE Set Advertising Parameters command + * -- Own_Address_Type: public: 0x00 (default), random: 0x01, resolvable-1: 0x02, resolvable-2: 0x03 + * > 7.8.10: LE Set Scan Parameters command + * -- Own_Address_Type: public: 0x00 (default), random: 0x01, resolvable-1: 0x02, resolvable-2: 0x03 + * + * +++ + * + * Couldn't yet find a reference to the L2CAP Address Type, which differs from HCI Address Type! + */ + enum Address_T : uint8_t { + HCIADDR_LE_PUBLIC = 0x00, + HCIADDR_LE_RANDOM = 0x01, + + L2CAPADDR_BREDR = 0x00, + L2CAPADDR_LE_PUBLIC = 0x01, + L2CAPADDR_LE_RANDOM = 0x02 + }; + +} // namespace direct_bt + +#endif /* BT_ADDRESS_HPP_ */ diff --git a/api/direct_bt/BTIoctl.hpp b/api/direct_bt/BTIoctl.hpp new file mode 100644 index 00000000..b410152b --- /dev/null +++ b/api/direct_bt/BTIoctl.hpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <[email protected]> + * Copyright (C) 2002-2010 Marcel Holtmann <[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. + * + * **************************************************************************************** + * **************************************************************************************** + * **************************************************************************************** + * + * This file imports certain information from Linux Kernel's BlueZ protocol stack, + * allowing the use of these kernel services via system calls. + * Therefore, the license has been aligned with this project. + * See file COPYING in the root folder of this project for more details, + * as well as Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl): + * <https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note>. + * + * Original sources: + * linux-kernel 4.19 include/net/bluetooth/bluetooth.h (git head 8d368fc58e7aeb42b39d7bec7c585efdfbc49074). + * + * Original copyright: + * BlueZ - Bluetooth protocol stack for Linux + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Written 2000,2001 by Maxim Krasnyansky <[email protected]> + */ + +#ifndef BT_IOCTL_HPP_ +#define BT_IOCTL_HPP_ + +#include "BTAddress.hpp" + +#include "linux_kernel_types.hpp" + +extern "C" { + #include <stdint.h> + #include <endian.h> + #include <byteswap.h> + #include <netinet/in.h> // Already exported named by OS +} /* extern "C" */ + +/** + * Information from include/net/bluetooth/bluetooth.h + */ + +#ifndef AF_BLUETOOTH +#define AF_BLUETOOTH 31 +#define PF_BLUETOOTH AF_BLUETOOTH +#endif + +/* Bluetooth versions */ +#define BLUETOOTH_VER_1_1 1 +#define BLUETOOTH_VER_1_2 2 +#define BLUETOOTH_VER_2_0 3 + +#define BTPROTO_L2CAP 0 +#define BTPROTO_HCI 1 +#define BTPROTO_SCO 2 +#define BTPROTO_RFCOMM 3 +#define BTPROTO_BNEP 4 +#define BTPROTO_CMTP 5 +#define BTPROTO_HIDP 6 +#define BTPROTO_AVDTP 7 + +#define SOL_HCI 0 +#define SOL_L2CAP 6 +#define SOL_SCO 17 +#define SOL_RFCOMM 18 + +#define BT_SECURITY 4 +struct bt_security { + __u8 level; + __u8 key_size; +}; +#define BT_SECURITY_SDP 0 +#define BT_SECURITY_LOW 1 +#define BT_SECURITY_MEDIUM 2 +#define BT_SECURITY_HIGH 3 +#define BT_SECURITY_FIPS 4 + +#define BT_DEFER_SETUP 7 + +#define BT_FLUSHABLE 8 + +#define BT_FLUSHABLE_OFF 0 +#define BT_FLUSHABLE_ON 1 + +#define BT_POWER 9 +struct bt_power { + __u8 force_active; +}; +#define BT_POWER_FORCE_ACTIVE_OFF 0 +#define BT_POWER_FORCE_ACTIVE_ON 1 + +#define BT_CHANNEL_POLICY 10 + +/* BR/EDR only (default policy) + * AMP controllers cannot be used. + * Channel move requests from the remote device are denied. + * If the L2CAP channel is currently using AMP, move the channel to BR/EDR. + */ +#define BT_CHANNEL_POLICY_BREDR_ONLY 0 + +/* BR/EDR Preferred + * Allow use of AMP controllers. + * If the L2CAP channel is currently on AMP, move it to BR/EDR. + * Channel move requests from the remote device are allowed. + */ +#define BT_CHANNEL_POLICY_BREDR_PREFERRED 1 + +/* AMP Preferred + * Allow use of AMP controllers + * If the L2CAP channel is currently on BR/EDR and AMP controller + * resources are available, initiate a channel move to AMP. + * Channel move requests from the remote device are allowed. + * If the L2CAP socket has not been connected yet, try to create + * and configure the channel directly on an AMP controller rather + * than BR/EDR. + */ +#define BT_CHANNEL_POLICY_AMP_PREFERRED 2 + +#define BT_VOICE 11 +struct bt_voice { + __u16 setting; +}; + +#define BT_VOICE_TRANSPARENT 0x0003 +#define BT_VOICE_CVSD_16BIT 0x0060 + +#define BT_SNDMTU 12 +#define BT_RCVMTU 13 + +/* Connection and socket states */ +enum { + BT_CONNECTED = 1, /* Equal to TCP_ESTABLISHED to make net code happy */ + BT_OPEN, + BT_BOUND, + BT_LISTEN, + BT_CONNECT, + BT_CONNECT2, + BT_CONFIG, + BT_DISCONN, + BT_CLOSED +}; + +/** + * Additional ordinary helpers .. + */ + +/* Byte order conversions */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobs(d) (d) +#define htobl(d) (d) +#define htobll(d) (d) +#define btohs(d) (d) +#define btohl(d) (d) +#define btohll(d) (d) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define htobs(d) bswap_16(d) +#define htobl(d) bswap_32(d) +#define htobll(d) bswap_64(d) +#define btohs(d) bswap_16(d) +#define btohl(d) bswap_32(d) +#define btohll(d) bswap_64(d) +#else +#error "Unknown byte order" +#endif + +/* Bluetooth unaligned access */ +#define bt_get_unaligned(ptr) \ +__extension__ ({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define bt_put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +#elif __BYTE_ORDER == __BIG_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} +#else +#error "Unknown byte order" +#endif + +#endif /* BT_IOCTL_HPP_ */ diff --git a/api/direct_bt/BTTypes.hpp b/api/direct_bt/BTTypes.hpp new file mode 100644 index 00000000..45acb205 --- /dev/null +++ b/api/direct_bt/BTTypes.hpp @@ -0,0 +1,406 @@ +/* + * 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. + */ + +#ifndef BT_TYPES_HPP_ +#define BT_TYPES_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include "BTAddress.hpp" +#include "UUID.hpp" + +namespace direct_bt { + + enum AD_Type_Const : uint8_t { + AD_FLAGS_LIMITED_MODE_BIT = 0x01, + AD_FLAGS_GENERAL_MODE_BIT = 0x02 + }; + + enum L2CAP_Channels : uint16_t { + L2CAP_CID_SIGNALING = 0x0001, + L2CAP_CID_CONN_LESS = 0x0002, + L2CAP_CID_A2MP = 0x0003, + + /* BT Core Spec v5.2: Vol 3, Part G GATT: 5.2.2 LE channel requirements */ + L2CAP_CID_ATT = 0x0004, + + L2CAP_CID_LE_SIGNALING = 0x0005, + L2CAP_CID_SMP = 0x0006, + L2CAP_CID_SMP_BREDR = 0x0007, + L2CAP_CID_DYN_START = 0x0040, + L2CAP_CID_DYN_END = 0xffff, + L2CAP_CID_LE_DYN_END = 0x007f + }; + + /** + * Protocol Service Multiplexers (PSM) Assigned numbers + * <https://www.bluetooth.com/specifications/assigned-numbers/logical-link-control/> + */ + enum L2CAP_PSM : uint16_t { + L2CAP_PSM_UNDEF = 0x0000, + L2CAP_PSM_SDP = 0x0001, + L2CAP_PSM_RFCOMM = 0x0003, + L2CAP_PSM_TCSBIN = 0x0005, + L2CAP_PSM_TCSBIN_CORDLESS = 0x0007, + L2CAP_PSM_BNEP = 0x000F, + L2CAP_PSM_HID_CONTROL = 0x0011, + L2CAP_PSM_HID_INTERRUPT = 0x0013, + L2CAP_PSM_UPNP = 0x0015, + L2CAP_PSM_AVCTP = 0x0017, + L2CAP_PSM_AVDTP = 0x0019, + L2CAP_PSM_AVCTP_BROWSING = 0x001B, + L2CAP_PSM_UDI_C_PLANE = 0x001D, + L2CAP_PSM_ATT = 0x001F, + L2CAP_PSM_LE_DYN_START = 0x0080, + L2CAP_PSM_LE_DYN_END = 0x00FF, + L2CAP_PSM_DYN_START = 0x1001, + L2CAP_PSM_DYN_END = 0xffff, + L2CAP_PSM_AUTO_END = 0x10ff + }; + + /** + * BT Core Spec v5.2: Vol 3, Part A L2CAP Spec: 6 State Machine + */ + enum L2CAP_States : uint8_t { + CLOSED, + WAIT_CONNECTED, + WAIT_CONNECTED_RSP, + CONFIG, + OPEN, + WAIT_DISCONNECTED, + WAIT_CREATE, + WAIT_CONNECT, + WAIT_CREATE_RSP, + WAIT_MOVE, + WAIT_MOVE_RSP, + WAIT_MOVE_CONFIRM, + WAIT_CONFIRM_RSP + }; + + /** + * BT Core Spec v5.2: Vol 3, Part A L2CAP Spec: 7.9 PRIORITIZING DATA OVER HCI + * + * In order for guaranteed channels to meet their guarantees, + * L2CAP should prioritize traffic over the HCI transport in devices that support HCI. + * Packets for Guaranteed channels should receive higher priority than packets for Best Effort channels. + */ + + /** + * ​​Assigned numbers are used in Generic Access Profile (GAP) for inquiry response, + * EIR data type values, manufacturer-specific data, advertising data, + * low energy UUIDs and appearance characteristics, and class of device. + * <p> + * Type identifier values as defined in "Assigned Numbers - Generic Access Profile" + * <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> + * </p> + * <p> + * Also see Bluetooth Core Specification Supplement V9, Part A: 1, p 9 pp + * for data format definitions. + * </p> + * <p> + * For data segment layout see Bluetooth Core Specification V5.2 [Vol. 3, Part C, 11, p 1392] + * </p> + * <p> + * https://www.bluetooth.com/specifications/archived-specifications/ + * </p> + */ + enum GAP_T : uint8_t { + // Last sync 2020-02-17 with <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> + /** Flags */ + FLAGS = 0x01, + /** Incomplete List of 16-bit Service Class UUID. (Supplement, Part A, section 1.1)*/ + UUID16_INCOMPLETE = 0x02, + /** Complete List of 16-bit Service Class UUID. (Supplement, Part A, section 1.1) */ + UUID16_COMPLETE = 0x03, + /** Incomplete List of 32-bit Service Class UUID. (Supplement, Part A, section 1.1) */ + UUID32_INCOMPLETE = 0x04, + /** Complete List of 32-bit Service Class UUID. (Supplement, Part A, section 1.1) */ + UUID32_COMPLETE = 0x05, + /** Incomplete List of 128-bit Service Class UUID. (Supplement, Part A, section 1.1) */ + UUID128_INCOMPLETE = 0x06, + /** Complete List of 128-bit Service Class UUID. (Supplement, Part A, section 1.1) */ + UUID128_COMPLETE = 0x07, + /** Shortened local name (Supplement, Part A, section 1.2) */ + NAME_LOCAL_SHORT = 0x08, + /** Complete local name (Supplement, Part A, section 1.2) */ + NAME_LOCAL_COMPLETE = 0x09, + /** Transmit power level (Supplement, Part A, section 1.5) */ + TX_POWER_LEVEL = 0x0A, + + /** + * SSP: Secure Simple Pairing Out of Band: Supplement, Part A, section 1.6 + * Supplement, Part A, Section 1.6: SSP OOB Data Block w/ SSP_OOB_LEN ([Vol 3] Part C, Section 5.2.2.7.) + * <p> + * SSP Class of device (Supplement, Part A, section 1.6). + * </p> + */ + SSP_CLASS_OF_DEVICE = 0x0D, + /** SSP: Simple Pairing Hash C and Simple Pairing Hash C-192 (Supplement, Part A 1.6) */ + SSP_HASH_C192 = 0x0E, + /** SSP: Simple Pairing Randomizer R-192 (Supplement, Part A, section 1.6) */ + SSP_RANDOMIZER_R192 = 0x0F, + + /** Device ID Profile v 1.3 or later */ + DEVICE_ID = 0x10, + + /** Security Manager TK Value (Supplement, Part A, section 1.8) */ + SEC_MGR_TK_VALUE = 0x10, + + /** Security Manager Out of Band Flags (Supplement, Part A, section 1.7) */ + SEC_MGR_OOB_FLAGS = 0x11, + + /** Slave Connection Interval Range */ + SLAVE_CONN_IVAL_RANGE = 0x12, + + /** List of 16-bit Service Solicitation UUIDs (Supplement, Part A, section 1.10) */ + SOLICIT_UUID16 = 0x14, + + /** List of 128-bit Service Solicitation UUIDs (Supplement, Part A, section 1.10) */ + SOLICIT_UUID128 = 0x15, + + /** Service Data - 16-bit UUID (Supplement, Part A, section 1.11) */ + SVC_DATA_UUID16 = 0x16, + + /* Public Target Address (Supplement, Part A, section 1.13) */ + PUB_TRGT_ADDR = 0x17, + /* Random Target Address (Supplement, Part A, section 1.14) */ + RND_TRGT_ADDR = 0x18, + + /** (GAP) Appearance (Supplement, Part A, section 1.12) */ + GAP_APPEARANCE = 0x19, + + /** Advertising Interval (Supplement, Part A, section 1.15) */ + ADV_INTERVAL = 0x1A, + /** LE Bluetooth Device Address */ + LE_BT_DEV_ADDRESS = 0x1B, + /** LE ROLE */ + LE_ROLE = 0x1C, + + /** SSP: Simple Pairing Hash C-256 (Supplement, Part A 1.6) */ + SSP_HASH_C256 = 0x1D, + /** SSP: Simple Pairing Randomizer R-256 (Supplement, Part A, section 1.6) */ + SSP_RANDOMIZER_R256 = 0x1E, + + /** List of 32-bit Service Solicitation UUID (Supplement, Part A, section 1.10) */ + SOLICIT_UUID32 = 0x1F, + + /** Service data, 32-bit UUID (Supplement, Part A, section 1.11) */ + SVC_DATA_UUID32 = 0x20, + /** Service data, 128-bit UUID (Supplement, Part A, section 1.11) */ + SVC_DATA_UUID128 = 0x21, + + /** SSP: LE Secure Connections Confirmation Value (Supplement Part A, Section 1.6) */ + SSP_LE_SEC_CONN_ACK_VALUE = 0x22, + /** SSP: LE Secure Connections Random Value (Supplement Part A, Section 1.6) */ + SSP_LE_SEC_CONN_RND_VALUE = 0x23, + + /* URI (Supplement, Part A, section 1.18) */ + URI = 0x24, + + /* Indoor Positioning - Indoor Positioning Service v1.0 or later */ + INDOOR_POSITIONING = 0x25, + + /* Transport Discovery Data - Transport Discovery Service v1.0 or later */ + TX_DISCOVERY_DATA = 0x26, + + /** LE Supported Features (Supplement, Part A, Section 1.19) */ + LE_SUPP_FEATURES = 0x27, + + CH_MAP_UPDATE_IND = 0x28, + PB_ADV = 0x29, + MESH_MESSAGE = 0x2A, + MESH_BEACON = 0x2B, + BIG_INFO = 0x2C, + BROADCAST_CODE = 0x2D, + INFO_DATA_3D = 0x3D, + + /** Manufacturer id code and specific opaque data */ + MANUFACTURE_SPECIFIC = 0xFF + }; + + + // ************************************************* + // ************************************************* + // ************************************************* + + class ManufactureSpecificData + { + public: + uint16_t const company; + std::string const companyName; + int const data_len; + std::shared_ptr<uint8_t> const data; + + ManufactureSpecificData() + : company(0), companyName(), data_len(0), data(nullptr) {} + + ManufactureSpecificData(uint16_t const company, uint8_t const * const data, int const data_len); + + std::string getCompanyString() const; + std::string toString() const; + }; + + // ************************************************* + // ************************************************* + // ************************************************* + + /** + * Collection of 'Advertising Data' (AD) + * or 'Extended Inquiry Response' (EIR) information. + */ + class EInfoReport + { + public: + enum class Source : int { + /** not available */ + NA, + /* Advertising Data (AD) */ + AD, + /** Extended Inquiry Response (EIR) */ + EIR + }; + enum class Element : uint32_t { + EVT_TYPE = (1 << 0), + BDADDR_TYPE = (1 << 1), + BDADDR = (1 << 2), + NAME = (1 << 3), + NAME_SHORT = (1 << 4), + RSSI = (1 << 5), + TX_POWER = (1 << 6), + MANUF_DATA = (1 << 7) + }; + + private: + Source source = Source::NA; + uint64_t timestamp = 0; + uint32_t data_set = 0; + + uint8_t evt_type = 0; + uint8_t mac_type = 0; + EUI48 mac; + + std::string name; + std::string name_short; + int8_t rssi = 0; + int8_t tx_power = 0; + std::shared_ptr<ManufactureSpecificData> msd = nullptr; + std::vector<std::shared_ptr<uuid_t>> services; + + void set(Element bit) { data_set |= static_cast<uint32_t>(bit); } + void setSource(Source s) { source = s; } + void setTimestamp(uint64_t ts) { timestamp = ts; } + void setEvtType(uint8_t et) { evt_type = et; set(Element::EVT_TYPE); } + void setAddressType(uint8_t at) { mac_type = at; set(Element::BDADDR_TYPE); } + void setAddress(EUI48 const &a) { mac = a; set(Element::BDADDR); } + void setName(const uint8_t *buffer, int buffer_len); + void setShortName(const uint8_t *buffer, int buffer_len); + void setRSSI(int8_t v) { rssi = v; set(Element::RSSI); } + void setTxPower(int8_t v) { tx_power = v; set(Element::TX_POWER); } + void setManufactureSpecificData(uint16_t const company, uint8_t const * const data, int const data_len) { + msd = std::shared_ptr<ManufactureSpecificData>(new ManufactureSpecificData(company, data, data_len)); + set(Element::MANUF_DATA); + } + + void addService(std::shared_ptr<uuid_t> const &uuid); + + + int 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); + + public: + static bool isSet(const uint32_t data_set, Element bit) { return 0 != (data_set & static_cast<uint32_t>(bit)); } + static std::string dataSetToString(const uint32_t data_Set); + + /** + * Reads a complete Advertising Data (AD) Report + * and returns the number of AD reports in form of a sharable list of EInfoReport; + * <p> + * See Bluetooth Core Specification V5.2 [Vol. 4, Part E, 7.7.65.2, p 2382] + * <p> + * https://www.bluetooth.com/specifications/archived-specifications/ + * </p> + */ + static std::vector<std::shared_ptr<EInfoReport>> read_ad_reports(uint8_t const * data, uint8_t const data_length); + + /** + * Reads the Extended Inquiry Response (EIR) or Advertising Data (AD) segments + * and returns the number of parsed data segments; + * <p> + * AD as well as EIR information is passed in little endian order + * in the same fashion data block: + * <pre> + * a -> { + * uint8_t len + * uint8_t type + * uint8_t data[len-1]; + * } + * b -> next block = a + 1 + len; + * </pre> + * </p> + * <p> + * See Bluetooth Core Specification V5.2 [Vol. 3, Part C, 11, p 1392] + * and Bluetooth Core Specification Supplement V9, Part A: 1, p 9 + 2 Examples, p25.. + * and "Assigned Numbers - Generic Access Profile" + * <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> + * </p> + * <p> + * https://www.bluetooth.com/specifications/archived-specifications/ + * </p> + */ + int read_data(uint8_t const * data, uint8_t const data_length); + + Source getSource() const { return source; } + uint64_t getTimestamp() const { return timestamp; } + bool isSet(Element bit) const { return 0 != (data_set & static_cast<uint32_t>(bit)); } + uint32_t getDataSet() const { return data_set; } + + uint8_t getEvtType() const { return evt_type; } + uint8_t getAddressType() const { return mac_type; } + EUI48 const & getAddress() const { return mac; } + std::string const & getName() const { return name; } + std::string const & getShortName() const { return name_short; } + int8_t getRSSI() const { return rssi; } + int8_t getTxPower() const { return tx_power; } + + std::shared_ptr<ManufactureSpecificData> getManufactureSpecificData() const { return msd; } + std::vector<std::shared_ptr<uuid_t>> getServices() const { return services; } + + std::string getSourceString() const; + std::string getAddressString() const { return mac.toString(); } + std::string dataSetToString() const; + std::string toString() const; + }; + + // ************************************************* + // ************************************************* + // ************************************************* + +} // namespace direct_bt + +#endif /* BT_TYPES_HPP_ */ diff --git a/api/tinyb_hci/BasicTypes.hpp b/api/direct_bt/BasicTypes.hpp index 2436547f..0da36a95 100644 --- a/api/tinyb_hci/BasicTypes.hpp +++ b/api/direct_bt/BasicTypes.hpp @@ -23,10 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef BASICTYPES_HPP_ -#define BASICTYPES_HPP_ +#ifndef BASIC_TYPES_HPP_ +#define BASIC_TYPES_HPP_ -#pragma once #include <cstring> #include <string> #include <memory> @@ -34,10 +33,11 @@ #include <vector> extern "C" { + #include <endian.h> #include <byteswap.h> } -namespace tinyb_hci { +namespace direct_bt { /** * Returns current monotonic time in milliseconds. @@ -48,7 +48,7 @@ namespace tinyb_hci { class RuntimeException : public std::exception { protected: - RuntimeException(std::string const type, std::string const m, const char* file=__FILE__, int line=__LINE__) noexcept + RuntimeException(std::string const type, std::string const m, const char* file, int line) noexcept : msg(std::string(type).append(" @ ").append(file).append(":").append(std::to_string(line)).append(": ").append(m)) { } public: @@ -80,12 +80,30 @@ namespace tinyb_hci { : RuntimeException("IllegalArgumentException", m, file, line) {} }; + class IllegalStateException : public RuntimeException { + public: + IllegalStateException(std::string const m, const char* file, int line) noexcept + : RuntimeException("IllegalStateException", m, file, line) {} + }; + + class InvalidStateException : public RuntimeException { + public: + InvalidStateException(std::string const m, const char* file, int line) noexcept + : RuntimeException("InvalidStateException", m, file, line) {} + }; + class UnsupportedOperationException : public RuntimeException { public: UnsupportedOperationException(std::string const m, const char* file, int line) noexcept : RuntimeException("UnsupportedOperationException", m, file, line) {} }; + class IndexOutOfBoundsException : public RuntimeException { + public: + IndexOutOfBoundsException(const int index, const int count, const int length, const char* file, int line) noexcept + : RuntimeException("IndexOutOfBoundsException", "Index "+std::to_string(index)+", count "+std::to_string(count)+", data length "+std::to_string(length), file, line) {} + }; + /** // ************************************************* // ************************************************* @@ -205,11 +223,26 @@ namespace tinyb_hci { #error "Unexpected __BYTE_ORDER" #endif + inline void put_uint8(uint8_t * buffer, int const byte_offset, const uint8_t v) + { + uint8_t * p = (uint8_t *) ( buffer + byte_offset ); + *p = v; + } + inline uint8_t get_uint8(uint8_t const * buffer, int const byte_offset) + { + uint8_t const * p = (uint8_t const *) ( buffer + byte_offset ); + return *p; + } inline void put_uint16(uint8_t * buffer, int const byte_offset, const uint16_t v) { uint16_t * p = (uint16_t *) ( buffer + byte_offset ); *p = v; } + inline void put_uint16(uint8_t * buffer, int const byte_offset, const uint16_t v, bool littleEndian) + { + uint16_t * p = (uint16_t *) ( buffer + byte_offset ); + *p = littleEndian ? cpu_to_le(v) : cpu_to_be(v); + } inline uint16_t get_uint16(uint8_t const * buffer, int const byte_offset) { uint16_t const * p = (uint16_t const *) ( buffer + byte_offset ); @@ -226,6 +259,11 @@ namespace tinyb_hci { uint32_t * p = (uint32_t *) ( buffer + byte_offset ); *p = v; } + inline void put_uint32(uint8_t * buffer, int const byte_offset, const uint32_t v, bool littleEndian) + { + uint32_t * p = (uint32_t *) ( buffer + byte_offset ); + *p = littleEndian ? cpu_to_le(v) : cpu_to_be(v); + } inline uint32_t get_uint32(uint8_t const * buffer, int const byte_offset) { uint32_t const * p = (uint32_t const *) ( buffer + byte_offset ); @@ -242,6 +280,11 @@ namespace tinyb_hci { uint128_t * p = (uint128_t *) ( buffer + byte_offset ); *p = v; } + inline void put_uint128(uint8_t * buffer, int const byte_offset, const uint128_t v, bool littleEndian) + { + uint128_t * p = (uint128_t *) ( buffer + byte_offset ); + *p = littleEndian ? cpu_to_le(v) : cpu_to_be(v); + } inline uint128_t get_uint128(uint8_t const * buffer, int const byte_offset) { uint128_t const * p = (uint128_t const *) ( buffer + byte_offset ); @@ -285,7 +328,7 @@ namespace tinyb_hci { * BE: uuid16 -> value.data[2+3] * </pre> */ - uint128_t merge_uint128(uint128_t const & base_uuid, uint16_t const uuid16, int const uuid16_le_octet_index); + uint128_t merge_uint128(uint16_t const uuid16, uint128_t const & base_uuid, int const uuid16_le_octet_index); /** * Merge the given 'uuid32' into a 'base_uuid' copy at the given little endian 'uuid32_le_octet_index' position. @@ -307,8 +350,30 @@ namespace tinyb_hci { * BE: uuid32 -> value.data[0..3] * </pre> */ - uint128_t merge_uint128(uint128_t const & base_uuid, uint32_t const uuid32, int const uuid32_le_octet_index); + uint128_t merge_uint128(uint32_t const uuid32, uint128_t const & base_uuid, int const uuid32_le_octet_index); + + std::string uint8HexString(const uint8_t v, const bool leading0X); + std::string uint16HexString(const uint16_t v, const bool leading0X); + std::string uint32HexString(const uint32_t v, const bool leading0X); + + /** + * If lsbFirst is true, orders LSB left -> MSB right, usual for byte streams. + * <p> + * Otherwise orders MSB left -> LSB right, usual for readable integer values. + * </p> + */ + std::string bytesHexString(const uint8_t * bytes, const int offset, const int length, const bool lsbFirst, const bool leading0X); + + std::string int32SeparatedString(const int32_t v, const char separator=','); + std::string uint32SeparatedString(const uint32_t v, const char separator=','); + std::string uint64SeparatedString(const uint64_t v, const char separator=','); + + /** trim in place */ + void trimInPlace(std::string &s); + + /** trim copy */ + std::string trimCopy(const std::string &s); -} // namespace tinyb_hci +} // namespace direct_bt -#endif /* BASICTYPES_HPP_ */ +#endif /* BASIC_TYPES_HPP_ */ diff --git a/api/direct_bt/GATTHandler.hpp b/api/direct_bt/GATTHandler.hpp new file mode 100644 index 00000000..7f4a2c9d --- /dev/null +++ b/api/direct_bt/GATTHandler.hpp @@ -0,0 +1,360 @@ +/* + * 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. + */ + +#ifndef GATT_HANDLER_HPP_ +#define GATT_HANDLER_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> +#include <thread> + +#include "UUID.hpp" +#include "BTTypes.hpp" +#include "HCITypes.hpp" +#include "L2CAPComm.hpp" +#include "ATTPDUTypes.hpp" +#include "GATTTypes.hpp" +#include "LFRingbuffer.hpp" + +/** + * BT Core Spec v5.2: Vol 3, Part F Attribute Protocol (ATT) + * BT Core Spec v5.2: Vol 3, Part G Generic Attribute Protocol (GATT) + * + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4 Summary of GATT Profile Attribute Types + */ + +namespace direct_bt { + + typedef std::shared_ptr<GATTCharacterisicsDecl> GATTCharacterisicsDeclRef; + + class GATTNotificationListener { + public: + virtual void notificationReceived(std::shared_ptr<HCIDevice> dev, GATTCharacterisicsDeclRef charDecl, + std::shared_ptr<const AttHandleValueRcv> charValue) = 0; + virtual ~GATTNotificationListener() {} + }; + class GATTIndicationListener { + public: + virtual void indicationReceived(std::shared_ptr<HCIDevice> dev, GATTCharacterisicsDeclRef charDecl, + std::shared_ptr<const AttHandleValueRcv> charValue, const bool confirmationSent) = 0; + virtual ~GATTIndicationListener() {} + }; + + /** + * Representing a complete Primary Service Declaration + * including its list of Characteristic Declarations, + * which also may include its client config if available. + */ + class GATTPrimaryService { + public: + /** The primary service declaration itself */ + const GATTUUIDHandleRange declaration; + /** List of Characteristic Declarations as shared reference */ + std::vector<GATTCharacterisicsDeclRef> characteristicDeclList; + + GATTPrimaryService(const GATTUUIDHandleRange serviceDecl) + : declaration(serviceDecl), characteristicDeclList() { + characteristicDeclList.reserve(10); + } + + std::string toString() const { + std::string res = declaration.toString()+"[ "; + for(size_t i=0; i<characteristicDeclList.size(); i++) { + if( 0 < i ) { + res += ", "; + } + res += std::to_string(i)+"[ "+characteristicDeclList.at(i)->toString()+" ]"; + } + res += " ]"; + return res; + } + }; + + typedef std::shared_ptr<GATTPrimaryService> GATTPrimaryServiceRef; + + class GATTHandler { + public: + enum State : int { + Error = -1, + Disconnected = 0, + Connecting = 1, + Connected = 2, + RequestInProgress = 3, + DiscoveringCharacteristics = 4, + GetClientCharaceristicConfiguration = 5, + WaitWriteResponse = 6, + WaitReadResponse = 7 + }; + + enum Defaults : int { + /* BT Core Spec v5.2: Vol 3, Part F 3.2.8: Maximum length of an attribute value. */ + ClientMaxMTU = 512, + + /* BT Core Spec v5.2: Vol 3, Part G GATT: 5.2.1 ATT_MTU */ + DEFAULT_MIN_ATT_MTU = 23, + + /** 10s poll timeout for l2cap reader thread */ + L2CAP_READER_THREAD_POLL_TIMEOUT = 10000, + ATTPDU_RING_CAPACITY = 256 + }; + + static std::string getStateString(const State state); + + private: + POctets rbuffer; + + State state; + std::shared_ptr<L2CAPComm> l2cap; + const int timeoutMS; + + LFRingbuffer<std::shared_ptr<const AttPDUMsg>, nullptr> attPDURing; + std::thread l2capReaderThread; + volatile bool l2capReaderRunning; + volatile bool l2capReaderShallStop; + + std::shared_ptr<GATTNotificationListener> gattNotificationListener = nullptr; + std::shared_ptr<GATTIndicationListener> gattIndicationListener = nullptr; + bool sendIndicationConfirmation = false; + + uint16_t serverMTU; + uint16_t usedMTU; + std::vector<GATTPrimaryServiceRef> services; + + State validateState(); + + void l2capReaderThreadImpl(); + std::shared_ptr<const AttPDUMsg> receiveNext(); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4.2 MTU Exchange + * <p> + * Returns the server-mtu if successful, otherwise 0. + * </p> + */ + uint16_t exchangeMTU(const uint16_t clientMaxMTU=ClientMaxMTU); + + public: + GATTHandler(std::shared_ptr<L2CAPComm> l2cap, const int timeoutMS) + : rbuffer(ClientMaxMTU), state(Disconnected), + l2cap(l2cap), timeoutMS(timeoutMS), + attPDURing(ATTPDU_RING_CAPACITY), l2capReaderRunning(false), l2capReaderShallStop(false), + serverMTU(DEFAULT_MIN_ATT_MTU), usedMTU(DEFAULT_MIN_ATT_MTU) {} + + GATTHandler(std::shared_ptr<HCIDevice> device, const int timeoutMS) + : rbuffer(ClientMaxMTU), state(Disconnected), + l2cap(new L2CAPComm(device, L2CAP_PSM_UNDEF, L2CAP_CID_ATT)), timeoutMS(timeoutMS), + attPDURing(ATTPDU_RING_CAPACITY), l2capReaderRunning(false), l2capReaderShallStop(false), + serverMTU(DEFAULT_MIN_ATT_MTU), usedMTU(DEFAULT_MIN_ATT_MTU) {} + + ~GATTHandler(); + + State getState() const { return state; } + std::string getStateString() const { return getStateString(state); } + + /** + * Replaces the GATTNotificationListener with the given instance, returning the replaced one. + */ + std::shared_ptr<GATTNotificationListener> setGATTNotificationListener(std::shared_ptr<GATTNotificationListener> l); + + /** + * Replaces the GATTNotificationListener with the given instance, returning the replaced one. + * <p> + * If {@code sendIndicationConfirmation} is {@code true}, a {@code ATT_HANDLE_VALUE_CFM} + * will be sent automatically right after receiving the event. + * </p> + */ + std::shared_ptr<GATTIndicationListener> setGATTIndicationListener(std::shared_ptr<GATTIndicationListener> l, bool sendConfirmation); + + /** + * After successful l2cap connection, the MTU will be exchanged. + * See getServerMTU() and getUsedMTU(), the latter is in use. + */ + bool connect(); + bool disconnect(); + bool isOpen() const { return Disconnected < state && l2cap->isOpen(); } + + bool send(const AttPDUMsg & msg); + + uint16_t getServerMTU() const { return serverMTU; } + uint16_t getUsedMTU() const { return usedMTU; } + + /** + * Find and return the GATTCharacterisicsDecl within internal primary services + * via given characteristic handle. + * <p> + * Returns nullptr if not found. + * </p> + */ + GATTCharacterisicsDeclRef findCharacterisics(const uint16_t charHandle); + + /** + * Find and return the GATTCharacterisicsDecl within given list of primary services + * via given characteristic handle. + * <p> + * Returns nullptr if not found. + * </p> + */ + GATTCharacterisicsDeclRef findCharacterisics(const uint16_t charHandle, std::vector<GATTPrimaryServiceRef> &services); + + /** + * Find and return the GATTCharacterisicsDecl within given primary service + * via given characteristic handle. + * <p> + * Returns nullptr if not found. + * </p> + */ + GATTCharacterisicsDeclRef findCharacterisics(const uint16_t charHandle, GATTPrimaryServiceRef service); + + /** + * Discover all primary services _and_ all its characteristics declarations + * including their client config. + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * </p> + * Method returns reference to GATTHandler internal data. + */ + std::vector<GATTPrimaryServiceRef> & discoverCompletePrimaryServices(); + + /** + * Discover all primary services _only_. + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * </p> + */ + bool discoverPrimaryServices(std::vector<GATTPrimaryServiceRef> & result); + + /** + * Discover all characteristics of a service and declaration attributes _only_. + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * </p> + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characterisic Declaration Attribute Value + * </p> + */ + bool discoverCharacteristics(GATTPrimaryServiceRef service); + + /** + * Discover all client characteristics config declaration _only_. + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * </p> + */ + bool discoverClientCharacteristicConfig(GATTPrimaryServiceRef service); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors + * <p> + * This is of little use, prefer discoverCharacteristics(..) and more elaborate GATTCharacterisicsDecl type! + * </p> + */ + bool discoverCharacteristicDescriptors(const GATTUUIDHandleRange & service, std::vector<GATTUUIDHandle> & result); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value + * </p> + * <p> + * If expectedLength = 0, then only one ATT_READ_REQ/RSP will be used. + * </p> + * <p> + * If expectedLength < 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used until + * the response returns zero. This is the default parameter. + * </p> + * <p> + * If expectedLength > 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used + * if required until the response returns zero. + * </p> + */ + bool readCharacteristicValue(const GATTCharacterisicsDecl & decl, POctets & res, int expectedLength=-1); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.2 Read Value Using Characteristic UUID + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value + * </p> + * <p> + * If expectedLength = 0, then only one ATT_READ_REQ/RSP will be used. + * </p> + * <p> + * If expectedLength < 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used until + * the response returns zero. This is the default parameter. + * </p> + * <p> + * If expectedLength > 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used + * if required until the response returns zero. + * </p> + */ + bool readCharacteristicValue(const GATTUUIDHandleRange & charUUIDHandleRange, POctets & res, int expectedLength=-1); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + * </p> + */ + bool writeClientCharacteristicConfigReq(const GATTClientCharacteristicConfigDecl & cccd, const TROOctets & value); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value Without Response + */ + bool writeClientCharacteristicConfigCmd(const GATTClientCharacteristicConfigDecl & cccd, const TROOctets & value); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value + */ + bool writeCharacteristicValueReq(const GATTCharacterisicsDecl & decl, const TROOctets & value); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value Without Response + */ + bool writeCharacteristicValueCmd(const GATTCharacterisicsDecl & decl, const TROOctets & value); + + /*****************************************************/ + /** Higher level semantic functionality **/ + /*****************************************************/ + + std::shared_ptr<GenericAccess> getGenericAccess(std::vector<GATTPrimaryServiceRef> & primServices); + std::shared_ptr<GenericAccess> getGenericAccess(std::vector<GATTCharacterisicsDeclRef> & genericAccessCharDeclList); + + std::shared_ptr<DeviceInformation> getDeviceInformation(std::vector<GATTPrimaryServiceRef> & primServices); + std::shared_ptr<DeviceInformation> getDeviceInformation(std::vector<GATTCharacterisicsDeclRef> & deviceInfoCharDeclList); + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + */ + bool configIndicationNotification(const GATTClientCharacteristicConfigDecl & decl, const bool enableNotification, const bool enableIndication); + }; + +} // namespace direct_bt + +#endif /* GATT_HANDLER_HPP_ */ diff --git a/api/direct_bt/GATTNumbers.hpp b/api/direct_bt/GATTNumbers.hpp new file mode 100644 index 00000000..246ac623 --- /dev/null +++ b/api/direct_bt/GATTNumbers.hpp @@ -0,0 +1,404 @@ +/* + * 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. + */ + +#ifndef GATT_NUMBERS_HPP_ +#define GATT_NUMBERS_HPP_ + +#include <cstdint> +#include "UUID.hpp" +#include "BasicTypes.hpp" +#include "OctetTypes.hpp" +#include "ieee11073/DataTypes.hpp" + +namespace direct_bt { + +/** + * Higher level GATT values for services and so forth ... + */ + +/** + * GATT Service Type, each encapsulating a set of Characteristics. + * <p> + * https://www.bluetooth.com/specifications/gatt/services/ + * </p> + */ +enum GattServiceType : uint16_t { + /** The generic_access service contains generic information about the device. All available Characteristics are readonly. */ + GENERIC_ACCESS = 0x1800, + /** The Health Thermometer service exposes temperature and other data from a thermometer intended for healthcare and fitness applications. */ + HEALTH_THERMOMETER = 0x1809, + /** The Device Information Service exposes manufacturer and/or vendor information about a device. */ + DEVICE_INFORMATION = 0x180A, + /** The Battery Service exposes the state of a battery within a device. */ + BATTERY_SERVICE = 0x180F, +}; +std::string GattServiceTypeToString(const GattServiceType v); + +/** + * GATT Assigned Characteristic Attribute Type for single logical value. + * <p> + * https://www.bluetooth.com/specifications/gatt/characteristics/ + * </p> + */ +enum GattCharacteristicType : uint16_t { + // + // GENERIC_ACCESS + // + DEVICE_NAME = 0x2A00, + APPEARANCE = 0x2A01, + PERIPHERAL_PRIVACY_FLAG = 0x2A02, + RECONNECTION_ADDRESS = 0x2A03, + PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = 0x2A04, + + /** Mandatory: sint16 10^-2: Celsius */ + TEMPERATURE = 0x2A6E, + + /** Mandatory: sint16 10^-1: Celsius */ + TEMPERATURE_CELSIUS = 0x2A1F, + TEMPERATURE_FAHRENHEIT = 0x2A20, + + // + // HEALTH_THERMOMETER + // + TEMPERATURE_MEASUREMENT = 0x2A1C, + /** Mandatory: 8bit: 1 armpit, 2 body (general), 3(ear), 4 (finger), ... */ + TEMPERATURE_TYPE = 0x2A1D, + INTERMEDIATE_TEMPERATURE = 0x2A1E, + MEASUREMENT_INTERVAL = 0x2A21, + + // + // DEVICE_INFORMATION + // + /** Mandatory: uint40 */ + SYSTEM_ID = 0x2A23, + MODEL_NUMBER_STRING = 0x2A24, + SERIAL_NUMBER_STRING = 0x2A25, + FIRMWARE_REVISION_STRING = 0x2A26, + HARDWARE_REVISION_STRING = 0x2A27, + SOFTWARE_REVISION_STRING = 0x2A28, + MANUFACTURER_NAME_STRING = 0x2A29, + REGULATORY_CERT_DATA_LIST = 0x2A2A, + PNP_ID = 0x2A50, +}; +std::string GattCharacteristicTypeToString(const GattCharacteristicType v); + +enum GattCharacteristicProperty : uint8_t { + Broadcast = 0x01, + Read = 0x02, + WriteNoAck = 0x04, + WriteWithAck = 0x08, + Notify = 0x10, + Indicate = 0x20, + AuthSignedWrite = 0x40, + ExtProps = 0x80, + /** FIXME: extension? */ + ReliableWriteExt = 0x81, + /** FIXME: extension? */ + AuxWriteExt = 0x82 +}; +std::string GattCharacteristicPropertyToString(const GattCharacteristicProperty v); + +enum GattRequirementSpec : uint8_t { + Excluded = 0x00, + Mandatory = 0x01, + Optional = 0x02, + Conditional = 0x03, + if_characteristic_supported = 0x11, + if_notify_or_indicate_supported = 0x12, + C1 = 0x21, +}; +std::string GattRequirementSpecToString(const GattRequirementSpec v); + +struct GattCharacteristicPropertySpec { + const GattCharacteristicProperty property; + const GattRequirementSpec requirement; + + std::string toString() const; +}; + +struct GattClientCharacteristicConfigSpec { + const GattRequirementSpec requirement; + const GattCharacteristicPropertySpec read; + const GattCharacteristicPropertySpec writeWithAck; + + std::string toString() const; +}; + +struct GattCharacteristicSpec { + const GattCharacteristicType characteristic; + const GattRequirementSpec requirement; + + enum PropertySpecIdx : int { + ReadIdx = 0, + WriteNoAckIdx, + WriteWithAckIdx, + AuthSignedWriteIdx, + ReliableWriteExtIdx, + NotifyIdx, + IndicateIdx, + AuxWriteExtIdx, + BroadcastIdx + }; + /** Aggregated in PropertySpecIdx order */ + const std::vector<GattCharacteristicPropertySpec> propertySpec; + + const GattClientCharacteristicConfigSpec clientConfig; + + std::string toString() const; +}; + +struct GattServiceCharacteristic { + const GattServiceType service; + const std::vector<GattCharacteristicSpec> characteristics; + + std::string toString() const; +}; + +/** + * Intentionally ease compile and linker burden by using 'extern' instead of 'inline', + * as the latter would require compile to crunch the structure + * and linker to chose where to place the actual artifact. + */ +extern const GattServiceCharacteristic GATT_GENERIC_ACCESS_SRVC; +extern const GattServiceCharacteristic GATT_HEALTH_THERMOMETER_SRVC; +extern const GattServiceCharacteristic GATT_DEVICE_INFORMATION_SRVC; +extern const std::vector<const GattServiceCharacteristic*> GATT_SERVICES; + +/** + * Find the GattServiceCharacteristic entry by given uuid16, + * denominating either a GattServiceType or GattCharacteristicType. + */ +const GattServiceCharacteristic * findGattServiceChar(const uint16_t uuid16); + +/** + * Find the GattCharacteristicSpec entry by given uuid16, + * denominating either a GattCharacteristicType. + */ +const GattCharacteristicSpec * findGattCharSpec(const uint16_t uuid16); + +/********************************************************/ +/********************************************************/ +/********************************************************/ + +/** + * Converts a GATT Name (not null-terminated) UTF8 to a null-terminated C++ string + */ +std::string GattNameToString(const TROOctets &v); + +struct PeriphalPreferredConnectionParameters { + /** mandatory [6..3200] x 1.25ms */ + const uint16_t minConnectionInterval; + /** mandatory [6..3200] x 1.25ms and >= minConnectionInterval */ + const uint16_t maxConnectionInterval; + /** mandatory [1..1000] */ + const uint16_t slaveLatency; + /** mandatory [10..3200] */ + const uint16_t connectionSupervisionTimeoutMultiplier; + + PeriphalPreferredConnectionParameters(const TROOctets &source); + std::string toString() const; +}; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.generic_access.xml */ +class GenericAccess { + public: + enum AppearanceCat : uint16_t { + UNKNOWN = 0, + GENERIC_PHONE = 64, + GENERIC_COMPUTER = 128, + GENERIC_WATCH = 192, + SPORTS_WATCH = 193, + GENERIC_CLOCK = 256, + GENERIC_DISPLAY = 320, + GENERIC_REMOTE_CLOCK = 384, + GENERIC_EYE_GLASSES = 448, + GENERIC_TAG = 512, + GENERIC_KEYRING = 576, + GENERIC_MEDIA_PLAYER = 640, + GENERIC_BARCODE_SCANNER = 704, + GENERIC_THERMOMETER = 768, + GENERIC_THERMOMETER_EAR = 769, + GENERIC_HEART_RATE_SENSOR = 832, + HEART_RATE_SENSOR_BELT = 833, + GENERIC_BLOD_PRESSURE = 896, + BLOD_PRESSURE_ARM = 897, + BLOD_PRESSURE_WRIST = 898, + HID = 960, + HID_KEYBOARD = 961, + HID_MOUSE = 962, + HID_JOYSTICK = 963, + HID_GAMEPAD = 964, + HID_DIGITIZER_TABLET = 965, + HID_CARD_READER = 966, + HID_DIGITAL_PEN = 967, + HID_BARCODE_SCANNER = 968, + GENERIC_GLUCOSE_METER = 1024, + GENERIC_RUNNING_WALKING_SENSOR = 1088, + RUNNING_WALKING_SENSOR_IN_SHOE = 1089, + RUNNING_WALKING_SENSOR_ON_SHOE = 1090, + RUNNING_WALKING_SENSOR_HIP = 1091, + GENERIC_CYCLING = 1152, + CYCLING_COMPUTER = 1153, + CYCLING_SPEED_SENSOR = 1154, + CYCLING_CADENCE_SENSOR = 1155, + CYCLING_POWER_SENSOR = 1156, + CYCLING_SPEED_AND_CADENCE_SENSOR = 1157, + GENERIC_PULSE_OXIMETER = 3136, + PULSE_OXIMETER_FINGERTIP = 3137, + PULSE_OXIMETER_WRIST = 3138, + GENERIC_WEIGHT_SCALE = 3200, + GENERIC_PERSONAL_MOBILITY_DEVICE = 3264, + PERSONAL_MOBILITY_DEVICE_WHEELCHAIR = 3265, + PERSONAL_MOBILITY_DEVICE_SCOOTER = 3266, + GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 3328, + GENERIC_INSULIN_PUMP = 3392, + INSULIN_PUMP_DURABLE = 3393, + INSULIN_PUMP_PATCH = 3396, + INSULIN_PUMP_PEN = 3400, + GENERIC_MEDICATION_DELIVERY = 3456, + GENERIC_OUTDOOR_SPORTS_ACTIVITY = 5184, + OUTDOOR_SPORTS_ACTIVITY_LOCATION_DISPLAY_DEVICE = 5185, + OUTDOOR_SPORTS_ACTIVITY_LOCATION_AND_NAVIGATION_DISPLAY_DEVICE = 5186, + OUTDOOR_SPORTS_ACTIVITY_LOCATION_POD = 5187, + OUTDOOR_SPORTS_ACTIVITY_LOCATION_AND_NAVIGATION_POD = 5188 + }; + static std::string AppearanceCatToString(const AppearanceCat v); + + const std::string deviceName; + const AppearanceCat category; + const PeriphalPreferredConnectionParameters prefConnParam; + + GenericAccess(const std::string & deviceName, const AppearanceCat category, const PeriphalPreferredConnectionParameters & prefConnParam) + : deviceName(deviceName), category(category), prefConnParam(prefConnParam) {} + + std::string toString() const; +}; + +struct PnP_ID { + const uint8_t vendor_id_source; + const uint16_t vendor_id; + const uint16_t product_id; + const uint16_t product_version; + + PnP_ID() + : vendor_id_source(0), vendor_id(0), product_id(0), product_version(0) {} + PnP_ID(const TROOctets &source); + PnP_ID(const uint8_t vendor_id_source, const uint16_t vendor_id, const uint16_t product_id, const uint16_t product_version) + : vendor_id_source(vendor_id_source), vendor_id(vendor_id), product_id(product_id), product_version(product_version) {} + + std::string toString() const; +}; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.device_information.xml */ +class DeviceInformation { + public: + const POctets systemID; + const std::string modelNumber; + const std::string serialNumber; + const std::string firmwareRevision; + const std::string hardwareRevision; + const std::string softwareRevision; + const std::string manufacturer; + const POctets regulatoryCertDataList; + const PnP_ID pnpID; + + DeviceInformation(const POctets &systemID, const std::string &modelNumber, const std::string &serialNumber, + const std::string &firmwareRevision, const std::string &hardwareRevision, const std::string &softwareRevision, + const std::string &manufacturer, const POctets ®ulatoryCertDataList, const PnP_ID &pnpID) + : systemID(systemID), modelNumber(modelNumber), serialNumber(serialNumber), firmwareRevision(firmwareRevision), + hardwareRevision(hardwareRevision), softwareRevision(softwareRevision), manufacturer(manufacturer), + regulatoryCertDataList(regulatoryCertDataList), pnpID(pnpID) {} + + std::string toString() const; +}; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.battery_service.xml */ +class BatteryService { + // TODO +}; + +/** https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.temperature_measurement.xml */ +class TemperatureMeasurementCharateristic { + public: + enum Bits : uint8_t { + /** bit 0: If set, temperature is in Fahrenheit, otherwise Celsius. */ + IS_TEMP_FAHRENHEIT = 1, + /** bit 1: If set, timestamp field present, otherwise not.. */ + HAS_TIMESTAMP = 2, + /** bit 2: If set, temperature type field present, otherwise not.. */ + HAS_TEMP_TYPE = 4 + }; + /** Bitfields of Bits. 1 byte. */ + const uint8_t flags; + + /** In Celsius if IS_TEMP_FAHRENHEIT is set, otherwise Fahrenheit. 4 bytes. */ + const float temperatureValue; + + /** Timestamp, if HAS_TIMESTAMP is set. 7 bytes(!?) here w/o fractions. */ + const ieee11073::AbsoluteTime timestamp; + + /** Temperature Type, if HAS_TEMP_TYPE is set: Format ????. 1 byte (!?). */ + const uint8_t temperature_type; + + static std::shared_ptr<TemperatureMeasurementCharateristic> get(const TROOctets &source); + static std::shared_ptr<TemperatureMeasurementCharateristic> get(const TOctetSlice &source) { + const TROOctets o(source.get_ptr(0), source.getSize()); + return get(o); + } + + TemperatureMeasurementCharateristic(const uint8_t flags, const float temperatureValue, const ieee11073::AbsoluteTime ×tamp, const uint8_t temperature_type) + : flags(flags), temperatureValue(temperatureValue), timestamp(timestamp), temperature_type(temperature_type) {} + + bool isFahrenheit() const { return 0 != ( flags & Bits::IS_TEMP_FAHRENHEIT ); } + bool hasTimestamp() const { return 0 != ( flags & Bits::HAS_TIMESTAMP ); } + bool hasTemperatureType() const { return 0 != ( flags & Bits::HAS_TEMP_TYPE ); } + + std::string toString() const; +}; + + +/* Application error */ + +#define ATT_ECODE_IO 0x80 +#define ATT_ECODE_TIMEOUT 0x81 +#define ATT_ECODE_ABORTED 0x82 + +#define ATT_MAX_VALUE_LEN 512 +#define ATT_DEFAULT_L2CAP_MTU 48 +#define ATT_DEFAULT_LE_MTU 23 + +/* Flags for Execute Write Request Operation */ + +#define ATT_CANCEL_ALL_PREP_WRITES 0x00 +#define ATT_WRITE_ALL_PREP_WRITES 0x01 + +/* Find Information Response Formats */ + +#define ATT_FIND_INFO_RESP_FMT_16BIT 0x01 +#define ATT_FIND_INFO_RESP_FMT_128BIT 0x02 + +} // namespace direct_bt + +#endif /* GATT_IOCTL_HPP_ */ diff --git a/api/direct_bt/GATTTypes.hpp b/api/direct_bt/GATTTypes.hpp new file mode 100644 index 00000000..8a891e97 --- /dev/null +++ b/api/direct_bt/GATTTypes.hpp @@ -0,0 +1,252 @@ +/* + * 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. + */ + +#ifndef GATT_TYPES_HPP_ +#define GATT_TYPES_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> + +#include "UUID.hpp" +#include "BTTypes.hpp" +#include "OctetTypes.hpp" +#include "ATTPDUTypes.hpp" + +/* Only to resolve high level service and characteristic names */ +#include "GATTNumbers.hpp" + +/** + * BT Core Spec v5.2: Vol 3, Part F Attribute Protocol (ATT) + * BT Core Spec v5.2: Vol 3, Part G Generic Attribute Protocol (GATT) + * + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4 Summary of GATT Profile Attribute Types + */ + +namespace direct_bt { + + /** + * Following UUID16 GATT profile attribute types are listed under: + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.4 Summary of GATT Profile Attribute Types + */ + enum GattAttributeType : uint16_t { + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services */ + PRIMARY_SERVICE = 0x2800, + /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service */ + CHARACTERISTIC = 0x2803, + /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */ + CLIENT_CHARACTERISTIC_CONFIGURATION = 0x2902 + }; + + /** + * uuid -> handle-range[ startHandle .. endHandle ] + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services + * + * Here the uuid is a service uuid + * and the handle-range it's characteristics-declaration + * </p> + */ + class GATTUUIDHandleRange { + public: + enum Type : uint8_t { + Service = 0, + Characteristic = 1 + }; + const Type type; + const uint16_t startHandle; + const uint16_t endHandle; + std::shared_ptr<const uuid_t> uuid; + + GATTUUIDHandleRange(const Type t, const uint16_t startHandle, const uint16_t endHandle, std::shared_ptr<const uuid_t> uuid) + : type(t), startHandle(startHandle), endHandle(endHandle), uuid(uuid) {} + + std::string toString() const { + std::string name = ""; + if( uuid_t::UUID16_SZ == uuid->getTypeSize() ) { + const uint16_t uuid16 = (static_cast<const uuid16_t*>(uuid.get()))->value; + if( Type::Service == type ) { + name = " - "+GattServiceTypeToString(static_cast<GattServiceType>(uuid16)); + } else if( Type::Characteristic == type ) { + name = " - "+GattCharacteristicTypeToString(static_cast<GattCharacteristicType>(uuid16)); + } + } + return "uuid "+uuid->toString()+", handle [ "+uint16HexString(startHandle, true)+".."+uint16HexString(endHandle, true)+" ]"+name; + } + }; + + /** + * handle -> value + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * + * Here the handle is a service's characteristics-declaration + * and the value the Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID. + * </p> + */ + class GATTHandleValuePair { + public: + const uint16_t handle; + const POctets value; + + GATTHandleValuePair(const uint16_t handle, const POctets & value) + : handle(handle), value(value) {} + + std::string toString() const { + return "handle "+uint16HexString(handle, true)+", value "+value.toString(); + } + }; + + /** + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration + */ + class GATTClientCharacteristicConfigDecl { + public: + /* Client Characteristics's Config Handle */ + const uint16_t handle; + + /* Client Characteristics's Config Value */ + uint16_t value; + + GATTClientCharacteristicConfigDecl(const uint16_t handle, const uint16_t value) + : handle(handle), value(value) {} + + std::string toString() const { + return "handle "+uint16HexString(handle, true)+", value "+uint16HexString(value, true); + } + }; + + /** + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value + * </p> + * handle -> CDAV value + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service + * + * Here the handle is a service's characteristics-declaration + * and the value the Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID. + * </p> + */ + class GATTCharacterisicsDecl { + public: + /** BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1.1 Characteristic Properties */ + enum PropertyBitVal : uint8_t { + Broadcast = 0x01, + Read = 0x02, + WriteNoAck = 0x04, + WriteWithAck = 0x08, + Notify = 0x10, + Indicate = 0x20, + AuthSignedWrite = 0x40, + ExtProps = 0x80 + }; + static std::string getPropertyString(const PropertyBitVal prop); + + static std::string getPropertiesString(const PropertyBitVal properties); + + /* Characteristics's Service UUID - key to service */ + std::shared_ptr<const uuid_t> service_uuid; + + /* Characteristics's Service Handle - key to service's handle range */ + const uint16_t service_handle; + /* Characteristics's Service Handle End of Range - key to service's handle range */ + const uint16_t service_handle_end; + + /* Characteristics Property */ + const PropertyBitVal properties; + /* Characteristics Value Handle */ + const uint16_t handle; + /* Characteristics UUID */ + std::shared_ptr<const uuid_t> uuid; + + /* Optional Client Characteristic Configuration declaration */ + std::shared_ptr<GATTClientCharacteristicConfigDecl> config; + + GATTCharacterisicsDecl(std::shared_ptr<const uuid_t> service_uuid, + const uint16_t service_handle, const uint16_t service_handle_end, + const PropertyBitVal properties, const uint16_t handle, std::shared_ptr<const uuid_t> uuid) + : service_uuid(service_uuid), service_handle(service_handle), service_handle_end(service_handle_end), + properties(properties), handle(handle), uuid(uuid), config(nullptr) {} + + bool hasProperties(const PropertyBitVal v) const { return v == ( properties & v ); } + + std::string getPropertiesString() const { + return getPropertiesString(properties); + } + std::string toString() const { + std::string service_name = ""; + std::string char_name = ""; + std::string config_str = ""; + if( uuid_t::UUID16_SZ == service_uuid->getTypeSize() ) { + const uint16_t uuid16 = (static_cast<const uuid16_t*>(service_uuid.get()))->value; + service_name = ", "+GattServiceTypeToString(static_cast<GattServiceType>(uuid16)); + } + if( uuid_t::UUID16_SZ == uuid->getTypeSize() ) { + const uint16_t uuid16 = (static_cast<const uuid16_t*>(uuid.get()))->value; + char_name = ", "+GattCharacteristicTypeToString(static_cast<GattCharacteristicType>(uuid16)); + } + if( nullptr != config ) { + config_str = ", config[ "+config->toString()+" ]"; + } + return "props "+uint8HexString(properties, true)+" "+getPropertiesString()+", handle "+uint16HexString(handle, true)+ + ", uuid "+uuid->toString()+char_name+config_str+ + ", service[ "+service_uuid->toString()+ + ", handle[ "+uint16HexString(service_handle, true)+".."+uint16HexString(service_handle_end, true)+" ]"+ + service_name+" ]"; + } + }; + + /** + * handle -> uuid + * <p> + * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors + * + * Here the handle references the characteristic-descriptor-declaration. + * The is the Characteristic Descriptor UUID. + * </p> + */ + class GATTUUIDHandle { + public: + const uint16_t handle; + std::shared_ptr<const uuid_t> uuid; + + GATTUUIDHandle(const uint16_t handle, std::shared_ptr<const uuid_t> uuid) + : handle(handle), uuid(uuid) {} + + std::string toString() const { + return "handle "+uint16HexString(handle, true)+"-> uuid "+uuid->toString(); + } + }; + + +} // namespace direct_bt + +#endif /* GATT_TYPES_HPP_ */ diff --git a/api/direct_bt/HCIComm.hpp b/api/direct_bt/HCIComm.hpp new file mode 100644 index 00000000..89d08471 --- /dev/null +++ b/api/direct_bt/HCIComm.hpp @@ -0,0 +1,167 @@ +/* + * 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. + */ + +#ifndef HCI_COMM_HPP_ +#define HCI_COMM_HPP_ + +#include <cstring> +#include <string> +#include <cstdint> +#include <memory> + +#include "BTTypes.hpp" +#include "BTIoctl.hpp" +#include "HCIIoctl.hpp" + +namespace direct_bt { + + enum HCIDefaults : int { + HCI_TO_SEND_REQ_POLL_MS = 1000 + }; + + enum HCI_Event_Types : uint8_t { + LE_Advertising_Report = 0x3E + }; + + class HCIComm { + private: + static int hci_open_dev(const uint16_t dev_id, const uint16_t channel); + static int hci_close_dev(int dd); + + const int timeoutMS; + const uint16_t dev_id; + const uint16_t channel; + int _dd; // the hci socket + uint16_t le_conn_handle; // LE connection handle + bool le_scanning; + + bool send_cmd(const uint16_t opcode, const void *command, const uint8_t command_len); + bool 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); + + bool le_set_scan_enable(const uint8_t enable, const uint8_t filter_dup); + bool 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_policy); + + public: + HCIComm(const uint16_t dev_id, const uint16_t channel, const int timeoutMS=HCI_TO_SEND_REQ_POLL_MS) + : timeoutMS(timeoutMS), dev_id(dev_id), channel(channel), _dd(-1), le_conn_handle(0), le_scanning(false) { + _dd = hci_open_dev(dev_id, channel); + } + ~HCIComm() { le_disable_scan(); le_disconnect(); close(); } + + void close(); + bool le_disconnect(const uint8_t reason=0); + + bool isOpen() const { return 0 <= _dd; } + int dd() const { return _dd; } + + bool isLEConnected() const { return 0 < le_conn_handle; } + uint16_t leConnHandle() const { return le_conn_handle; } + + void le_disable_scan(); + bool le_enable_scan(const uint8_t own_type=HCIADDR_LE_PUBLIC, + const uint16_t interval=0x0004, const uint16_t window=0x0004); + + uint16_t le_create_conn(const EUI48 &peer_bdaddr, + const uint8_t peer_mac_type=HCIADDR_LE_PUBLIC, + const uint8_t own_mac_type=HCIADDR_LE_PUBLIC, + const uint16_t interval=0x0004, const uint16_t window=0x0004, + const uint16_t min_interval=0x000F, const uint16_t max_interval=0x000F, + const uint16_t latency=0x0000, const uint16_t supervision_timeout=0x0C80, + const uint16_t min_ce_length=0x0001, const uint16_t max_ce_length=0x0001, + const uint8_t initiator_filter=0); + + private: + static inline void set_bit(int nr, void *addr) + { + *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31)); + } + + static inline void clear_bit(int nr, void *addr) + { + *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31)); + } + + static inline int test_bit(int nr, void *addr) + { + return *((uint32_t *) addr + (nr >> 5)) & (1 << (nr & 31)); + } + + public: + static inline void filter_clear(hci_ufilter *f) + { + memset(f, 0, sizeof(*f)); + } + static inline void filter_set_ptype(int t, hci_ufilter *f) + { + set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); + } + static inline void filter_clear_ptype(int t, hci_ufilter *f) + { + clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); + } + static inline int filter_test_ptype(int t, hci_ufilter *f) + { + return test_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); + } + static inline void filter_all_ptypes(hci_ufilter *f) + { + memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask)); + } + static inline void filter_set_event(int e, hci_ufilter *f) + { + set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); + } + static inline void filter_clear_event(int e, hci_ufilter *f) + { + clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); + } + static inline int filter_test_event(int e, hci_ufilter *f) + { + return test_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); + } + static inline void filter_all_events(hci_ufilter *f) + { + memset((void *) f->event_mask, 0xff, sizeof(f->event_mask)); + } + static inline void filter_set_opcode(int opcode, hci_ufilter *f) + { + f->opcode = opcode; + } + static inline void filter_clear_opcode(hci_ufilter *f) + { + f->opcode = 0; + } + static inline int filter_test_opcode(int opcode, hci_ufilter *f) + { + return (f->opcode == opcode); + } + }; + +} // namespace direct_bt + +#endif /* HCI_COMM_HPP_ */ diff --git a/api/direct_bt/HCIIoctl.hpp b/api/direct_bt/HCIIoctl.hpp new file mode 100644 index 00000000..a1d382a5 --- /dev/null +++ b/api/direct_bt/HCIIoctl.hpp @@ -0,0 +1,2452 @@ +/* + * 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. + * + * **************************************************************************************** + * **************************************************************************************** + * **************************************************************************************** + * + * This file imports certain information from Linux Kernel's BlueZ protocol stack, + * allowing the use of these kernel services via system calls. + * Therefore, the license has been aligned with this project. + * See file COPYING in the root folder of this project for more details, + * as well as Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl): + * <https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note>. + * + * Original sources: + * linux-kernel 4.19 include/net/bluetooth/hci.h (git head 8603d49906b231bbcd9141db7d096fa1041bc379). + * linux-kernel 4.19 include/net/bluetooth/hci_sock.h (git head ac71494934c475e3f51e5e3e64a12f57618d82a4). + * + * Original copyright: + * BlueZ - Bluetooth protocol stack for Linux + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Written 2000,2001 by Maxim Krasnyansky <[email protected]> + */ +#ifndef HCI_IOCTL_HPP_ +#define HCI_IOCTL_HPP_ + +#include "BTAddress.hpp" + +#include "linux_kernel_types.hpp" + +extern "C" { + #include <stdint.h> + #include <sys/socket.h> +} /* extern "C" */ + +/** + * Missing information ??? + */ +#define HCI_TYPE_LEN 1 +#define OGF_LINK_CTL 0x01 +#define OGF_LE_CTL 0x08 + +/** + * Information from include/net/bluetooth/hci.h + */ +#define HCI_MAX_ACL_SIZE 1024 +#define HCI_MAX_SCO_SIZE 255 +#define HCI_MAX_EVENT_SIZE 260 +#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) + +#define HCI_LINK_KEY_SIZE 16 +#define HCI_AMP_LINK_KEY_SIZE (2 * HCI_LINK_KEY_SIZE) + +#define HCI_MAX_AMP_ASSOC_SIZE 672 + +#define HCI_MAX_CSB_DATA_SIZE 252 + +/* HCI dev events */ +#define HCI_DEV_REG 1 +#define HCI_DEV_UNREG 2 +#define HCI_DEV_UP 3 +#define HCI_DEV_DOWN 4 +#define HCI_DEV_SUSPEND 5 +#define HCI_DEV_RESUME 6 +#define HCI_DEV_OPEN 7 +#define HCI_DEV_CLOSE 8 +#define HCI_DEV_SETUP 9 + +/* HCI notify events */ +#define HCI_NOTIFY_CONN_ADD 1 +#define HCI_NOTIFY_CONN_DEL 2 +#define HCI_NOTIFY_VOICE_SETTING 3 + +/* HCI bus types */ +#define HCI_VIRTUAL 0 +#define HCI_USB 1 +#define HCI_PCCARD 2 +#define HCI_UART 3 +#define HCI_RS232 4 +#define HCI_PCI 5 +#define HCI_SDIO 6 +#define HCI_SPI 7 +#define HCI_I2C 8 +#define HCI_SMD 9 + +/* HCI controller types */ +#define HCI_PRIMARY 0x00 +#define HCI_AMP 0x01 + +/* First BR/EDR Controller shall have ID = 0 */ +#define AMP_ID_BREDR 0x00 + +/* AMP controller types */ +#define AMP_TYPE_BREDR 0x00 +#define AMP_TYPE_80211 0x01 + +/* AMP controller status */ +#define AMP_STATUS_POWERED_DOWN 0x00 +#define AMP_STATUS_BLUETOOTH_ONLY 0x01 +#define AMP_STATUS_NO_CAPACITY 0x02 +#define AMP_STATUS_LOW_CAPACITY 0x03 +#define AMP_STATUS_MEDIUM_CAPACITY 0x04 +#define AMP_STATUS_HIGH_CAPACITY 0x05 +#define AMP_STATUS_FULL_CAPACITY 0x06 + +/* HCI device quirks */ +enum { + /* When this quirk is set, the HCI Reset command is send when + * closing the transport instead of when opening it. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_RESET_ON_CLOSE, + + /* When this quirk is set, the device is turned into a raw-only + * device and it will stay in unconfigured state. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_RAW_DEVICE, + + /* When this quirk is set, the buffer sizes reported by + * HCI Read Buffer Size command are corrected if invalid. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_FIXUP_BUFFER_SIZE, + + /* When this quirk is set, then a controller that does not + * indicate support for Inquiry Result with RSSI is assumed to + * support it anyway. Some early Bluetooth 1.2 controllers had + * wrongly configured local features that will require forcing + * them to enable this mode. Getting RSSI information with the + * inquiry responses is preferred since it allows for a better + * user expierence. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_FIXUP_INQUIRY_MODE, + + /* When this quirk is set, then the HCI Read Local Supported + * Commands command is not supported. In general Bluetooth 1.2 + * and later controllers should support this command. However + * some controllers indicate Bluetooth 1.2 support, but do + * not support this command. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_BROKEN_LOCAL_COMMANDS, + + /* When this quirk is set, then no stored link key handling + * is performed. This is mainly due to the fact that the + * HCI Delete Stored Link Key command is advertised, but + * not supported. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_BROKEN_STORED_LINK_KEY, + + /* When this quirk is set, an external configuration step + * is required and will be indicated with the controller + * configuation. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_EXTERNAL_CONFIG, + + /* When this quirk is set, the public Bluetooth address + * initially reported by HCI Read BD Address command + * is considered invalid. Controller configuration is + * required before this device can be used. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_INVALID_BDADDR, + + /* When this quirk is set, the duplicate filtering during + * scanning is based on Bluetooth devices addresses. To allow + * RSSI based updates, restart scanning if needed. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_STRICT_DUPLICATE_FILTER, + + /* When this quirk is set, LE scan and BR/EDR inquiry is done + * simultaneously, otherwise it's interleaved. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_SIMULTANEOUS_DISCOVERY, + + /* When this quirk is set, the enabling of diagnostic mode is + * not persistent over HCI Reset. Every time the controller + * is brought up it needs to be reprogrammed. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_NON_PERSISTENT_DIAG, + + /* When this quirk is set, setup() would be run after every + * open() and not just after the first open(). + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + * + */ + HCI_QUIRK_NON_PERSISTENT_SETUP, +}; + +/* HCI device flags */ +enum { + HCI_UP, + HCI_INIT, + HCI_RUNNING, + + HCI_PSCAN, + HCI_ISCAN, + HCI_AUTH, + HCI_ENCRYPT, + HCI_INQUIRY, + + HCI_RAW, + + HCI_RESET, +}; + +/* HCI socket flags */ +enum { + HCI_SOCK_TRUSTED, + HCI_MGMT_INDEX_EVENTS, + HCI_MGMT_UNCONF_INDEX_EVENTS, + HCI_MGMT_EXT_INDEX_EVENTS, + HCI_MGMT_EXT_INFO_EVENTS, + HCI_MGMT_OPTION_EVENTS, + HCI_MGMT_SETTING_EVENTS, + HCI_MGMT_DEV_CLASS_EVENTS, + HCI_MGMT_LOCAL_NAME_EVENTS, + HCI_MGMT_OOB_DATA_EVENTS, +}; + +/* + * BR/EDR and/or LE controller flags: the flags defined here should represent + * states from the controller. + */ +enum { + HCI_SETUP, + HCI_CONFIG, + HCI_AUTO_OFF, + HCI_RFKILLED, + HCI_MGMT, + HCI_BONDABLE, + HCI_SERVICE_CACHE, + HCI_KEEP_DEBUG_KEYS, + HCI_USE_DEBUG_KEYS, + HCI_UNREGISTER, + HCI_UNCONFIGURED, + HCI_USER_CHANNEL, + HCI_EXT_CONFIGURED, + HCI_LE_ADV, + HCI_LE_SCAN, + HCI_SSP_ENABLED, + HCI_SC_ENABLED, + HCI_SC_ONLY, + HCI_PRIVACY, + HCI_LIMITED_PRIVACY, + HCI_RPA_EXPIRED, + HCI_RPA_RESOLVING, + HCI_HS_ENABLED, + HCI_LE_ENABLED, + HCI_ADVERTISING, + HCI_ADVERTISING_CONNECTABLE, + HCI_CONNECTABLE, + HCI_DISCOVERABLE, + HCI_LIMITED_DISCOVERABLE, + HCI_LINK_SECURITY, + HCI_PERIODIC_INQ, + HCI_FAST_CONNECTABLE, + HCI_BREDR_ENABLED, + HCI_LE_SCAN_INTERRUPTED, + + HCI_DUT_MODE, + HCI_VENDOR_DIAG, + HCI_FORCE_BREDR_SMP, + HCI_FORCE_STATIC_ADDR, + HCI_LL_RPA_RESOLUTION, + HCI_CMD_PENDING, + + __HCI_NUM_FLAGS, +}; + +/* HCI timeouts */ +#define HCI_DISCONN_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ +#define HCI_PAIRING_TIMEOUT msecs_to_jiffies(60000) /* 60 seconds */ +#define HCI_INIT_TIMEOUT msecs_to_jiffies(10000) /* 10 seconds */ +#define HCI_CMD_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ +#define HCI_ACL_TX_TIMEOUT msecs_to_jiffies(45000) /* 45 seconds */ +#define HCI_AUTO_OFF_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ +#define HCI_POWER_OFF_TIMEOUT msecs_to_jiffies(5000) /* 5 seconds */ +#define HCI_LE_CONN_TIMEOUT msecs_to_jiffies(20000) /* 20 seconds */ +#define HCI_LE_AUTOCONN_TIMEOUT msecs_to_jiffies(4000) /* 4 seconds */ + +/* HCI data types */ +#define HCI_COMMAND_PKT 0x01 +#define HCI_ACLDATA_PKT 0x02 +#define HCI_SCODATA_PKT 0x03 +#define HCI_EVENT_PKT 0x04 +#define HCI_DIAG_PKT 0xf0 +#define HCI_VENDOR_PKT 0xff + +/* HCI packet types */ +#define HCI_DM1 0x0008 +#define HCI_DM3 0x0400 +#define HCI_DM5 0x4000 +#define HCI_DH1 0x0010 +#define HCI_DH3 0x0800 +#define HCI_DH5 0x8000 + +/* HCI packet types inverted masks */ +#define HCI_2DH1 0x0002 +#define HCI_3DH1 0x0004 +#define HCI_2DH3 0x0100 +#define HCI_3DH3 0x0200 +#define HCI_2DH5 0x1000 +#define HCI_3DH5 0x2000 + +#define HCI_HV1 0x0020 +#define HCI_HV2 0x0040 +#define HCI_HV3 0x0080 + +#define SCO_PTYPE_MASK (HCI_HV1 | HCI_HV2 | HCI_HV3) +#define ACL_PTYPE_MASK (~SCO_PTYPE_MASK) + +/* eSCO packet types */ +#define ESCO_HV1 0x0001 +#define ESCO_HV2 0x0002 +#define ESCO_HV3 0x0004 +#define ESCO_EV3 0x0008 +#define ESCO_EV4 0x0010 +#define ESCO_EV5 0x0020 +#define ESCO_2EV3 0x0040 +#define ESCO_3EV3 0x0080 +#define ESCO_2EV5 0x0100 +#define ESCO_3EV5 0x0200 + +#define SCO_ESCO_MASK (ESCO_HV1 | ESCO_HV2 | ESCO_HV3) +#define EDR_ESCO_MASK (ESCO_2EV3 | ESCO_3EV3 | ESCO_2EV5 | ESCO_3EV5) + +/* ACL flags */ +#define ACL_START_NO_FLUSH 0x00 +#define ACL_CONT 0x01 +#define ACL_START 0x02 +#define ACL_COMPLETE 0x03 +#define ACL_ACTIVE_BCAST 0x04 +#define ACL_PICO_BCAST 0x08 + +/* Baseband links */ +#define SCO_LINK 0x00 +#define ACL_LINK 0x01 +#define ESCO_LINK 0x02 +/* Low Energy links do not have defined link type. Use invented one */ +#define LE_LINK 0x80 +#define AMP_LINK 0x81 +#define INVALID_LINK 0xff + +/* LMP features */ +#define LMP_3SLOT 0x01 +#define LMP_5SLOT 0x02 +#define LMP_ENCRYPT 0x04 +#define LMP_SOFFSET 0x08 +#define LMP_TACCURACY 0x10 +#define LMP_RSWITCH 0x20 +#define LMP_HOLD 0x40 +#define LMP_SNIFF 0x80 + +#define LMP_PARK 0x01 +#define LMP_RSSI 0x02 +#define LMP_QUALITY 0x04 +#define LMP_SCO 0x08 +#define LMP_HV2 0x10 +#define LMP_HV3 0x20 +#define LMP_ULAW 0x40 +#define LMP_ALAW 0x80 + +#define LMP_CVSD 0x01 +#define LMP_PSCHEME 0x02 +#define LMP_PCONTROL 0x04 +#define LMP_TRANSPARENT 0x08 + +#define LMP_EDR_2M 0x02 +#define LMP_EDR_3M 0x04 +#define LMP_RSSI_INQ 0x40 +#define LMP_ESCO 0x80 + +#define LMP_EV4 0x01 +#define LMP_EV5 0x02 +#define LMP_NO_BREDR 0x20 +#define LMP_LE 0x40 +#define LMP_EDR_3SLOT 0x80 + +#define LMP_EDR_5SLOT 0x01 +#define LMP_SNIFF_SUBR 0x02 +#define LMP_PAUSE_ENC 0x04 +#define LMP_EDR_ESCO_2M 0x20 +#define LMP_EDR_ESCO_3M 0x40 +#define LMP_EDR_3S_ESCO 0x80 + +#define LMP_EXT_INQ 0x01 +#define LMP_SIMUL_LE_BR 0x02 +#define LMP_SIMPLE_PAIR 0x08 +#define LMP_NO_FLUSH 0x40 + +#define LMP_LSTO 0x01 +#define LMP_INQ_TX_PWR 0x02 +#define LMP_EXTFEATURES 0x80 + +/* Extended LMP features */ +#define LMP_CSB_MASTER 0x01 +#define LMP_CSB_SLAVE 0x02 +#define LMP_SYNC_TRAIN 0x04 +#define LMP_SYNC_SCAN 0x08 + +#define LMP_SC 0x01 +#define LMP_PING 0x02 + +/* Host features */ +#define LMP_HOST_SSP 0x01 +#define LMP_HOST_LE 0x02 +#define LMP_HOST_LE_BREDR 0x04 +#define LMP_HOST_SC 0x08 + +/* LE features */ +#define HCI_LE_ENCRYPTION 0x01 +#define HCI_LE_CONN_PARAM_REQ_PROC 0x02 +#define HCI_LE_SLAVE_FEATURES 0x08 +#define HCI_LE_PING 0x10 +#define HCI_LE_DATA_LEN_EXT 0x20 +#define HCI_LE_PHY_2M 0x01 +#define HCI_LE_PHY_CODED 0x08 +#define HCI_LE_EXT_ADV 0x10 +#define HCI_LE_EXT_SCAN_POLICY 0x80 +#define HCI_LE_PHY_2M 0x01 +#define HCI_LE_PHY_CODED 0x08 +#define HCI_LE_CHAN_SEL_ALG2 0x40 + +/* Connection modes */ +#define HCI_CM_ACTIVE 0x0000 +#define HCI_CM_HOLD 0x0001 +#define HCI_CM_SNIFF 0x0002 +#define HCI_CM_PARK 0x0003 + +/* Link policies */ +#define HCI_LP_RSWITCH 0x0001 +#define HCI_LP_HOLD 0x0002 +#define HCI_LP_SNIFF 0x0004 +#define HCI_LP_PARK 0x0008 + +/* Link modes */ +#define HCI_LM_ACCEPT 0x8000 +#define HCI_LM_MASTER 0x0001 +#define HCI_LM_AUTH 0x0002 +#define HCI_LM_ENCRYPT 0x0004 +#define HCI_LM_TRUSTED 0x0008 +#define HCI_LM_RELIABLE 0x0010 +#define HCI_LM_SECURE 0x0020 +#define HCI_LM_FIPS 0x0040 + +/* Authentication types */ +#define HCI_AT_NO_BONDING 0x00 +#define HCI_AT_NO_BONDING_MITM 0x01 +#define HCI_AT_DEDICATED_BONDING 0x02 +#define HCI_AT_DEDICATED_BONDING_MITM 0x03 +#define HCI_AT_GENERAL_BONDING 0x04 +#define HCI_AT_GENERAL_BONDING_MITM 0x05 + +/* I/O capabilities */ +#define HCI_IO_DISPLAY_ONLY 0x00 +#define HCI_IO_DISPLAY_YESNO 0x01 +#define HCI_IO_KEYBOARD_ONLY 0x02 +#define HCI_IO_NO_INPUT_OUTPUT 0x03 + +/* Link Key types */ +#define HCI_LK_COMBINATION 0x00 +#define HCI_LK_LOCAL_UNIT 0x01 +#define HCI_LK_REMOTE_UNIT 0x02 +#define HCI_LK_DEBUG_COMBINATION 0x03 +#define HCI_LK_UNAUTH_COMBINATION_P192 0x04 +#define HCI_LK_AUTH_COMBINATION_P192 0x05 +#define HCI_LK_CHANGED_COMBINATION 0x06 +#define HCI_LK_UNAUTH_COMBINATION_P256 0x07 +#define HCI_LK_AUTH_COMBINATION_P256 0x08 + +/* ---- HCI Error Codes ---- */ +#define HCI_ERROR_UNKNOWN_CONN_ID 0x02 +#define HCI_ERROR_AUTH_FAILURE 0x05 +#define HCI_ERROR_PIN_OR_KEY_MISSING 0x06 +#define HCI_ERROR_MEMORY_EXCEEDED 0x07 +#define HCI_ERROR_CONNECTION_TIMEOUT 0x08 +#define HCI_ERROR_REJ_LIMITED_RESOURCES 0x0d +#define HCI_ERROR_REJ_BAD_ADDR 0x0f +#define HCI_ERROR_REMOTE_USER_TERM 0x13 +#define HCI_ERROR_REMOTE_LOW_RESOURCES 0x14 +#define HCI_ERROR_REMOTE_POWER_OFF 0x15 +#define HCI_ERROR_LOCAL_HOST_TERM 0x16 +#define HCI_ERROR_PAIRING_NOT_ALLOWED 0x18 +#define HCI_ERROR_INVALID_LL_PARAMS 0x1e +#define HCI_ERROR_UNSPECIFIED 0x1f +#define HCI_ERROR_ADVERTISING_TIMEOUT 0x3c + +/* Flow control modes */ +#define HCI_FLOW_CTL_MODE_PACKET_BASED 0x00 +#define HCI_FLOW_CTL_MODE_BLOCK_BASED 0x01 + +/* The core spec defines 127 as the "not available" value */ +#define HCI_TX_POWER_INVALID 127 +#define HCI_RSSI_INVALID 127 + +#define HCI_ROLE_MASTER 0x00 +#define HCI_ROLE_SLAVE 0x01 + +/* Extended Inquiry Response field types */ +#define EIR_FLAGS 0x01 /* flags */ +#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define EIR_NAME_SHORT 0x08 /* shortened local name */ +#define EIR_NAME_COMPLETE 0x09 /* complete local name */ +#define EIR_TX_POWER 0x0A /* transmit power level */ +#define EIR_CLASS_OF_DEV 0x0D /* Class of Device */ +#define EIR_SSP_HASH_C192 0x0E /* Simple Pairing Hash C-192 */ +#define EIR_SSP_RAND_R192 0x0F /* Simple Pairing Randomizer R-192 */ +#define EIR_DEVICE_ID 0x10 /* device ID */ +#define EIR_APPEARANCE 0x19 /* Device appearance */ +#define EIR_LE_BDADDR 0x1B /* LE Bluetooth device address */ +#define EIR_LE_ROLE 0x1C /* LE role */ +#define EIR_SSP_HASH_C256 0x1D /* Simple Pairing Hash C-256 */ +#define EIR_SSP_RAND_R256 0x1E /* Simple Pairing Rand R-256 */ +#define EIR_LE_SC_CONFIRM 0x22 /* LE SC Confirmation Value */ +#define EIR_LE_SC_RANDOM 0x23 /* LE SC Random Value */ + +/* Low Energy Advertising Flags */ +#define LE_AD_LIMITED 0x01 /* Limited Discoverable */ +#define LE_AD_GENERAL 0x02 /* General Discoverable */ +#define LE_AD_NO_BREDR 0x04 /* BR/EDR not supported */ +#define LE_AD_SIM_LE_BREDR_CTRL 0x08 /* Simultaneous LE & BR/EDR Controller */ +#define LE_AD_SIM_LE_BREDR_HOST 0x10 /* Simultaneous LE & BR/EDR Host */ + +/* ----- HCI Commands ---- */ +#define HCI_OP_NOP 0x0000 + +#define HCI_OP_INQUIRY 0x0401 +struct hci_cp_inquiry { + __u8 lap[3]; + __u8 length; + __u8 num_rsp; +} __packed; + +#define HCI_OP_INQUIRY_CANCEL 0x0402 + +#define HCI_OP_PERIODIC_INQ 0x0403 + +#define HCI_OP_EXIT_PERIODIC_INQ 0x0404 + +#define HCI_OP_CREATE_CONN 0x0405 +struct hci_cp_create_conn { + bdaddr_t bdaddr; + __le16 pkt_type; + __u8 pscan_rep_mode; + __u8 pscan_mode; + __le16 clock_offset; + __u8 role_switch; +} __packed; + +#define HCI_OP_DISCONNECT 0x0406 +struct hci_cp_disconnect { + __le16 handle; + __u8 reason; +} __packed; + +#define HCI_OP_ADD_SCO 0x0407 +struct hci_cp_add_sco { + __le16 handle; + __le16 pkt_type; +} __packed; + +#define HCI_OP_CREATE_CONN_CANCEL 0x0408 +struct hci_cp_create_conn_cancel { + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_ACCEPT_CONN_REQ 0x0409 +struct hci_cp_accept_conn_req { + bdaddr_t bdaddr; + __u8 role; +} __packed; + +#define HCI_OP_REJECT_CONN_REQ 0x040a +struct hci_cp_reject_conn_req { + bdaddr_t bdaddr; + __u8 reason; +} __packed; + +#define HCI_OP_LINK_KEY_REPLY 0x040b +struct hci_cp_link_key_reply { + bdaddr_t bdaddr; + __u8 link_key[HCI_LINK_KEY_SIZE]; +} __packed; + +#define HCI_OP_LINK_KEY_NEG_REPLY 0x040c +struct hci_cp_link_key_neg_reply { + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_PIN_CODE_REPLY 0x040d +struct hci_cp_pin_code_reply { + bdaddr_t bdaddr; + __u8 pin_len; + __u8 pin_code[16]; +} __packed; +struct hci_rp_pin_code_reply { + __u8 status; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_PIN_CODE_NEG_REPLY 0x040e +struct hci_cp_pin_code_neg_reply { + bdaddr_t bdaddr; +} __packed; +struct hci_rp_pin_code_neg_reply { + __u8 status; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_CHANGE_CONN_PTYPE 0x040f +struct hci_cp_change_conn_ptype { + __le16 handle; + __le16 pkt_type; +} __packed; + +#define HCI_OP_AUTH_REQUESTED 0x0411 +struct hci_cp_auth_requested { + __le16 handle; +} __packed; + +#define HCI_OP_SET_CONN_ENCRYPT 0x0413 +struct hci_cp_set_conn_encrypt { + __le16 handle; + __u8 encrypt; +} __packed; + +#define HCI_OP_CHANGE_CONN_LINK_KEY 0x0415 +struct hci_cp_change_conn_link_key { + __le16 handle; +} __packed; + +#define HCI_OP_REMOTE_NAME_REQ 0x0419 +struct hci_cp_remote_name_req { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_mode; + __le16 clock_offset; +} __packed; + +#define HCI_OP_REMOTE_NAME_REQ_CANCEL 0x041a +struct hci_cp_remote_name_req_cancel { + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_READ_REMOTE_FEATURES 0x041b +struct hci_cp_read_remote_features { + __le16 handle; +} __packed; + +#define HCI_OP_READ_REMOTE_EXT_FEATURES 0x041c +struct hci_cp_read_remote_ext_features { + __le16 handle; + __u8 page; +} __packed; + +#define HCI_OP_READ_REMOTE_VERSION 0x041d +struct hci_cp_read_remote_version { + __le16 handle; +} __packed; + +#define HCI_OP_READ_CLOCK_OFFSET 0x041f +struct hci_cp_read_clock_offset { + __le16 handle; +} __packed; + +#define HCI_OP_SETUP_SYNC_CONN 0x0428 +struct hci_cp_setup_sync_conn { + __le16 handle; + __le32 tx_bandwidth; + __le32 rx_bandwidth; + __le16 max_latency; + __le16 voice_setting; + __u8 retrans_effort; + __le16 pkt_type; +} __packed; + +#define HCI_OP_ACCEPT_SYNC_CONN_REQ 0x0429 +struct hci_cp_accept_sync_conn_req { + bdaddr_t bdaddr; + __le32 tx_bandwidth; + __le32 rx_bandwidth; + __le16 max_latency; + __le16 content_format; + __u8 retrans_effort; + __le16 pkt_type; +} __packed; + +#define HCI_OP_REJECT_SYNC_CONN_REQ 0x042a +struct hci_cp_reject_sync_conn_req { + bdaddr_t bdaddr; + __u8 reason; +} __packed; + +#define HCI_OP_IO_CAPABILITY_REPLY 0x042b +struct hci_cp_io_capability_reply { + bdaddr_t bdaddr; + __u8 capability; + __u8 oob_data; + __u8 authentication; +} __packed; + +#define HCI_OP_USER_CONFIRM_REPLY 0x042c +struct hci_cp_user_confirm_reply { + bdaddr_t bdaddr; +} __packed; +struct hci_rp_user_confirm_reply { + __u8 status; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_USER_CONFIRM_NEG_REPLY 0x042d + +#define HCI_OP_USER_PASSKEY_REPLY 0x042e +struct hci_cp_user_passkey_reply { + bdaddr_t bdaddr; + __le32 passkey; +} __packed; + +#define HCI_OP_USER_PASSKEY_NEG_REPLY 0x042f + +#define HCI_OP_REMOTE_OOB_DATA_REPLY 0x0430 +struct hci_cp_remote_oob_data_reply { + bdaddr_t bdaddr; + __u8 hash[16]; + __u8 rand[16]; +} __packed; + +#define HCI_OP_REMOTE_OOB_DATA_NEG_REPLY 0x0433 +struct hci_cp_remote_oob_data_neg_reply { + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_IO_CAPABILITY_NEG_REPLY 0x0434 +struct hci_cp_io_capability_neg_reply { + bdaddr_t bdaddr; + __u8 reason; +} __packed; + +#define HCI_OP_CREATE_PHY_LINK 0x0435 +struct hci_cp_create_phy_link { + __u8 phy_handle; + __u8 key_len; + __u8 key_type; + __u8 key[HCI_AMP_LINK_KEY_SIZE]; +} __packed; + +#define HCI_OP_ACCEPT_PHY_LINK 0x0436 +struct hci_cp_accept_phy_link { + __u8 phy_handle; + __u8 key_len; + __u8 key_type; + __u8 key[HCI_AMP_LINK_KEY_SIZE]; +} __packed; + +#define HCI_OP_DISCONN_PHY_LINK 0x0437 +struct hci_cp_disconn_phy_link { + __u8 phy_handle; + __u8 reason; +} __packed; + +struct ext_flow_spec { + __u8 id; + __u8 stype; + __le16 msdu; + __le32 sdu_itime; + __le32 acc_lat; + __le32 flush_to; +} __packed; + +#define HCI_OP_CREATE_LOGICAL_LINK 0x0438 +#define HCI_OP_ACCEPT_LOGICAL_LINK 0x0439 +struct hci_cp_create_accept_logical_link { + __u8 phy_handle; + struct ext_flow_spec tx_flow_spec; + struct ext_flow_spec rx_flow_spec; +} __packed; + +#define HCI_OP_DISCONN_LOGICAL_LINK 0x043a +struct hci_cp_disconn_logical_link { + __le16 log_handle; +} __packed; + +#define HCI_OP_LOGICAL_LINK_CANCEL 0x043b +struct hci_cp_logical_link_cancel { + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +struct hci_rp_logical_link_cancel { + __u8 status; + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +#define HCI_OP_SET_CSB 0x0441 +struct hci_cp_set_csb { + __u8 enable; + __u8 lt_addr; + __u8 lpo_allowed; + __le16 packet_type; + __le16 interval_min; + __le16 interval_max; + __le16 csb_sv_tout; +} __packed; +struct hci_rp_set_csb { + __u8 status; + __u8 lt_addr; + __le16 interval; +} __packed; + +#define HCI_OP_START_SYNC_TRAIN 0x0443 + +#define HCI_OP_REMOTE_OOB_EXT_DATA_REPLY 0x0445 +struct hci_cp_remote_oob_ext_data_reply { + bdaddr_t bdaddr; + __u8 hash192[16]; + __u8 rand192[16]; + __u8 hash256[16]; + __u8 rand256[16]; +} __packed; + +#define HCI_OP_SNIFF_MODE 0x0803 +struct hci_cp_sniff_mode { + __le16 handle; + __le16 max_interval; + __le16 min_interval; + __le16 attempt; + __le16 timeout; +} __packed; + +#define HCI_OP_EXIT_SNIFF_MODE 0x0804 +struct hci_cp_exit_sniff_mode { + __le16 handle; +} __packed; + +#define HCI_OP_ROLE_DISCOVERY 0x0809 +struct hci_cp_role_discovery { + __le16 handle; +} __packed; +struct hci_rp_role_discovery { + __u8 status; + __le16 handle; + __u8 role; +} __packed; + +#define HCI_OP_SWITCH_ROLE 0x080b +struct hci_cp_switch_role { + bdaddr_t bdaddr; + __u8 role; +} __packed; + +#define HCI_OP_READ_LINK_POLICY 0x080c +struct hci_cp_read_link_policy { + __le16 handle; +} __packed; +struct hci_rp_read_link_policy { + __u8 status; + __le16 handle; + __le16 policy; +} __packed; + +#define HCI_OP_WRITE_LINK_POLICY 0x080d +struct hci_cp_write_link_policy { + __le16 handle; + __le16 policy; +} __packed; +struct hci_rp_write_link_policy { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_OP_READ_DEF_LINK_POLICY 0x080e +struct hci_rp_read_def_link_policy { + __u8 status; + __le16 policy; +} __packed; + +#define HCI_OP_WRITE_DEF_LINK_POLICY 0x080f +struct hci_cp_write_def_link_policy { + __le16 policy; +} __packed; + +#define HCI_OP_SNIFF_SUBRATE 0x0811 +struct hci_cp_sniff_subrate { + __le16 handle; + __le16 max_latency; + __le16 min_remote_timeout; + __le16 min_local_timeout; +} __packed; + +#define HCI_OP_SET_EVENT_MASK 0x0c01 + +#define HCI_OP_RESET 0x0c03 + +#define HCI_OP_SET_EVENT_FLT 0x0c05 +struct hci_cp_set_event_flt { + __u8 flt_type; + __u8 cond_type; + __u8 condition[0]; +} __packed; + +/* Filter types */ +#define HCI_FLT_CLEAR_ALL 0x00 +#define HCI_FLT_INQ_RESULT 0x01 +#define HCI_FLT_CONN_SETUP 0x02 + +/* CONN_SETUP Condition types */ +#define HCI_CONN_SETUP_ALLOW_ALL 0x00 +#define HCI_CONN_SETUP_ALLOW_CLASS 0x01 +#define HCI_CONN_SETUP_ALLOW_BDADDR 0x02 + +/* CONN_SETUP Conditions */ +#define HCI_CONN_SETUP_AUTO_OFF 0x01 +#define HCI_CONN_SETUP_AUTO_ON 0x02 + +#define HCI_OP_READ_STORED_LINK_KEY 0x0c0d +struct hci_cp_read_stored_link_key { + bdaddr_t bdaddr; + __u8 read_all; +} __packed; +struct hci_rp_read_stored_link_key { + __u8 status; + __u8 max_keys; + __u8 num_keys; +} __packed; + +#define HCI_OP_DELETE_STORED_LINK_KEY 0x0c12 +struct hci_cp_delete_stored_link_key { + bdaddr_t bdaddr; + __u8 delete_all; +} __packed; +struct hci_rp_delete_stored_link_key { + __u8 status; + __u8 num_keys; +} __packed; + +#define HCI_MAX_NAME_LENGTH 248 + +#define HCI_OP_WRITE_LOCAL_NAME 0x0c13 +struct hci_cp_write_local_name { + __u8 name[HCI_MAX_NAME_LENGTH]; +} __packed; + +#define HCI_OP_READ_LOCAL_NAME 0x0c14 +struct hci_rp_read_local_name { + __u8 status; + __u8 name[HCI_MAX_NAME_LENGTH]; +} __packed; + +#define HCI_OP_WRITE_CA_TIMEOUT 0x0c16 + +#define HCI_OP_WRITE_PG_TIMEOUT 0x0c18 + +#define HCI_OP_WRITE_SCAN_ENABLE 0x0c1a + #define SCAN_DISABLED 0x00 + #define SCAN_INQUIRY 0x01 + #define SCAN_PAGE 0x02 + +#define HCI_OP_READ_AUTH_ENABLE 0x0c1f + +#define HCI_OP_WRITE_AUTH_ENABLE 0x0c20 + #define AUTH_DISABLED 0x00 + #define AUTH_ENABLED 0x01 + +#define HCI_OP_READ_ENCRYPT_MODE 0x0c21 + +#define HCI_OP_WRITE_ENCRYPT_MODE 0x0c22 + #define ENCRYPT_DISABLED 0x00 + #define ENCRYPT_P2P 0x01 + #define ENCRYPT_BOTH 0x02 + +#define HCI_OP_READ_CLASS_OF_DEV 0x0c23 +struct hci_rp_read_class_of_dev { + __u8 status; + __u8 dev_class[3]; +} __packed; + +#define HCI_OP_WRITE_CLASS_OF_DEV 0x0c24 +struct hci_cp_write_class_of_dev { + __u8 dev_class[3]; +} __packed; + +#define HCI_OP_READ_VOICE_SETTING 0x0c25 +struct hci_rp_read_voice_setting { + __u8 status; + __le16 voice_setting; +} __packed; + +#define HCI_OP_WRITE_VOICE_SETTING 0x0c26 +struct hci_cp_write_voice_setting { + __le16 voice_setting; +} __packed; + +#define HCI_OP_HOST_BUFFER_SIZE 0x0c33 +struct hci_cp_host_buffer_size { + __le16 acl_mtu; + __u8 sco_mtu; + __le16 acl_max_pkt; + __le16 sco_max_pkt; +} __packed; + +#define HCI_OP_READ_NUM_SUPPORTED_IAC 0x0c38 +struct hci_rp_read_num_supported_iac { + __u8 status; + __u8 num_iac; +} __packed; + +#define HCI_OP_READ_CURRENT_IAC_LAP 0x0c39 + +#define HCI_OP_WRITE_CURRENT_IAC_LAP 0x0c3a +struct hci_cp_write_current_iac_lap { + __u8 num_iac; + __u8 iac_lap[6]; +} __packed; + +#define HCI_OP_WRITE_INQUIRY_MODE 0x0c45 + +#define HCI_MAX_EIR_LENGTH 240 + +#define HCI_OP_WRITE_EIR 0x0c52 +struct hci_cp_write_eir { + __u8 fec; + __u8 data[HCI_MAX_EIR_LENGTH]; +} __packed; + +#define HCI_OP_READ_SSP_MODE 0x0c55 +struct hci_rp_read_ssp_mode { + __u8 status; + __u8 mode; +} __packed; + +#define HCI_OP_WRITE_SSP_MODE 0x0c56 +struct hci_cp_write_ssp_mode { + __u8 mode; +} __packed; + +#define HCI_OP_READ_LOCAL_OOB_DATA 0x0c57 +struct hci_rp_read_local_oob_data { + __u8 status; + __u8 hash[16]; + __u8 rand[16]; +} __packed; + +#define HCI_OP_READ_INQ_RSP_TX_POWER 0x0c58 +struct hci_rp_read_inq_rsp_tx_power { + __u8 status; + __s8 tx_power; +} __packed; + +#define HCI_OP_SET_EVENT_MASK_PAGE_2 0x0c63 + +#define HCI_OP_READ_LOCATION_DATA 0x0c64 + +#define HCI_OP_READ_FLOW_CONTROL_MODE 0x0c66 +struct hci_rp_read_flow_control_mode { + __u8 status; + __u8 mode; +} __packed; + +#define HCI_OP_WRITE_LE_HOST_SUPPORTED 0x0c6d +struct hci_cp_write_le_host_supported { + __u8 le; + __u8 simul; +} __packed; + +#define HCI_OP_SET_RESERVED_LT_ADDR 0x0c74 +struct hci_cp_set_reserved_lt_addr { + __u8 lt_addr; +} __packed; +struct hci_rp_set_reserved_lt_addr { + __u8 status; + __u8 lt_addr; +} __packed; + +#define HCI_OP_DELETE_RESERVED_LT_ADDR 0x0c75 +struct hci_cp_delete_reserved_lt_addr { + __u8 lt_addr; +} __packed; +struct hci_rp_delete_reserved_lt_addr { + __u8 status; + __u8 lt_addr; +} __packed; + +#define HCI_OP_SET_CSB_DATA 0x0c76 +struct hci_cp_set_csb_data { + __u8 lt_addr; + __u8 fragment; + __u8 data_length; + __u8 data[HCI_MAX_CSB_DATA_SIZE]; +} __packed; +struct hci_rp_set_csb_data { + __u8 status; + __u8 lt_addr; +} __packed; + +#define HCI_OP_READ_SYNC_TRAIN_PARAMS 0x0c77 + +#define HCI_OP_WRITE_SYNC_TRAIN_PARAMS 0x0c78 +struct hci_cp_write_sync_train_params { + __le16 interval_min; + __le16 interval_max; + __le32 sync_train_tout; + __u8 service_data; +} __packed; +struct hci_rp_write_sync_train_params { + __u8 status; + __le16 sync_train_int; +} __packed; + +#define HCI_OP_READ_SC_SUPPORT 0x0c79 +struct hci_rp_read_sc_support { + __u8 status; + __u8 support; +} __packed; + +#define HCI_OP_WRITE_SC_SUPPORT 0x0c7a +struct hci_cp_write_sc_support { + __u8 support; +} __packed; + +#define HCI_OP_READ_LOCAL_OOB_EXT_DATA 0x0c7d +struct hci_rp_read_local_oob_ext_data { + __u8 status; + __u8 hash192[16]; + __u8 rand192[16]; + __u8 hash256[16]; + __u8 rand256[16]; +} __packed; + +#define HCI_OP_READ_LOCAL_VERSION 0x1001 +struct hci_rp_read_local_version { + __u8 status; + __u8 hci_ver; + __le16 hci_rev; + __u8 lmp_ver; + __le16 manufacturer; + __le16 lmp_subver; +} __packed; + +#define HCI_OP_READ_LOCAL_COMMANDS 0x1002 +struct hci_rp_read_local_commands { + __u8 status; + __u8 commands[64]; +} __packed; + +#define HCI_OP_READ_LOCAL_FEATURES 0x1003 +struct hci_rp_read_local_features { + __u8 status; + __u8 features[8]; +} __packed; + +#define HCI_OP_READ_LOCAL_EXT_FEATURES 0x1004 +struct hci_cp_read_local_ext_features { + __u8 page; +} __packed; +struct hci_rp_read_local_ext_features { + __u8 status; + __u8 page; + __u8 max_page; + __u8 features[8]; +} __packed; + +#define HCI_OP_READ_BUFFER_SIZE 0x1005 +struct hci_rp_read_buffer_size { + __u8 status; + __le16 acl_mtu; + __u8 sco_mtu; + __le16 acl_max_pkt; + __le16 sco_max_pkt; +} __packed; + +#define HCI_OP_READ_BD_ADDR 0x1009 +struct hci_rp_read_bd_addr { + __u8 status; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_READ_DATA_BLOCK_SIZE 0x100a +struct hci_rp_read_data_block_size { + __u8 status; + __le16 max_acl_len; + __le16 block_len; + __le16 num_blocks; +} __packed; + +#define HCI_OP_READ_LOCAL_CODECS 0x100b + +#define HCI_OP_READ_PAGE_SCAN_ACTIVITY 0x0c1b +struct hci_rp_read_page_scan_activity { + __u8 status; + __le16 interval; + __le16 window; +} __packed; + +#define HCI_OP_WRITE_PAGE_SCAN_ACTIVITY 0x0c1c +struct hci_cp_write_page_scan_activity { + __le16 interval; + __le16 window; +} __packed; + +#define HCI_OP_READ_TX_POWER 0x0c2d +struct hci_cp_read_tx_power { + __le16 handle; + __u8 type; +} __packed; +struct hci_rp_read_tx_power { + __u8 status; + __le16 handle; + __s8 tx_power; +} __packed; + +#define HCI_OP_READ_PAGE_SCAN_TYPE 0x0c46 +struct hci_rp_read_page_scan_type { + __u8 status; + __u8 type; +} __packed; + +#define HCI_OP_WRITE_PAGE_SCAN_TYPE 0x0c47 + #define PAGE_SCAN_TYPE_STANDARD 0x00 + #define PAGE_SCAN_TYPE_INTERLACED 0x01 + +#define HCI_OP_READ_RSSI 0x1405 +struct hci_cp_read_rssi { + __le16 handle; +} __packed; +struct hci_rp_read_rssi { + __u8 status; + __le16 handle; + __s8 rssi; +} __packed; + +#define HCI_OP_READ_CLOCK 0x1407 +struct hci_cp_read_clock { + __le16 handle; + __u8 which; +} __packed; +struct hci_rp_read_clock { + __u8 status; + __le16 handle; + __le32 clock; + __le16 accuracy; +} __packed; + +#define HCI_OP_READ_ENC_KEY_SIZE 0x1408 +struct hci_cp_read_enc_key_size { + __le16 handle; +} __packed; +struct hci_rp_read_enc_key_size { + __u8 status; + __le16 handle; + __u8 key_size; +} __packed; + +#define HCI_OP_READ_LOCAL_AMP_INFO 0x1409 +struct hci_rp_read_local_amp_info { + __u8 status; + __u8 amp_status; + __le32 total_bw; + __le32 max_bw; + __le32 min_latency; + __le32 max_pdu; + __u8 amp_type; + __le16 pal_cap; + __le16 max_assoc_size; + __le32 max_flush_to; + __le32 be_flush_to; +} __packed; + +#define HCI_OP_READ_LOCAL_AMP_ASSOC 0x140a +struct hci_cp_read_local_amp_assoc { + __u8 phy_handle; + __le16 len_so_far; + __le16 max_len; +} __packed; +struct hci_rp_read_local_amp_assoc { + __u8 status; + __u8 phy_handle; + __le16 rem_len; + __u8 frag[0]; +} __packed; + +#define HCI_OP_WRITE_REMOTE_AMP_ASSOC 0x140b +struct hci_cp_write_remote_amp_assoc { + __u8 phy_handle; + __le16 len_so_far; + __le16 rem_len; + __u8 frag[0]; +} __packed; +struct hci_rp_write_remote_amp_assoc { + __u8 status; + __u8 phy_handle; +} __packed; + +#define HCI_OP_GET_MWS_TRANSPORT_CONFIG 0x140c + +#define HCI_OP_ENABLE_DUT_MODE 0x1803 + +#define HCI_OP_WRITE_SSP_DEBUG_MODE 0x1804 + +#define HCI_OP_LE_SET_EVENT_MASK 0x2001 +struct hci_cp_le_set_event_mask { + __u8 mask[8]; +} __packed; + +#define HCI_OP_LE_READ_BUFFER_SIZE 0x2002 +struct hci_rp_le_read_buffer_size { + __u8 status; + __le16 le_mtu; + __u8 le_max_pkt; +} __packed; + +#define HCI_OP_LE_READ_LOCAL_FEATURES 0x2003 +struct hci_rp_le_read_local_features { + __u8 status; + __u8 features[8]; +} __packed; + +#define HCI_OP_LE_SET_RANDOM_ADDR 0x2005 + +#define HCI_OP_LE_SET_ADV_PARAM 0x2006 +struct hci_cp_le_set_adv_param { + __le16 min_interval; + __le16 max_interval; + __u8 type; + __u8 own_address_type; + __u8 direct_addr_type; + bdaddr_t direct_addr; + __u8 channel_map; + __u8 filter_policy; +} __packed; + +#define HCI_OP_LE_READ_ADV_TX_POWER 0x2007 +struct hci_rp_le_read_adv_tx_power { + __u8 status; + __s8 tx_power; +} __packed; + +#define HCI_MAX_AD_LENGTH 31 + +#define HCI_OP_LE_SET_ADV_DATA 0x2008 +struct hci_cp_le_set_adv_data { + __u8 length; + __u8 data[HCI_MAX_AD_LENGTH]; +} __packed; + +#define HCI_OP_LE_SET_SCAN_RSP_DATA 0x2009 +struct hci_cp_le_set_scan_rsp_data { + __u8 length; + __u8 data[HCI_MAX_AD_LENGTH]; +} __packed; + +#define HCI_OP_LE_SET_ADV_ENABLE 0x200a + +#define LE_SCAN_PASSIVE 0x00 +#define LE_SCAN_ACTIVE 0x01 + +#define HCI_OP_LE_SET_SCAN_PARAM 0x200b +struct hci_cp_le_set_scan_param { + __u8 type; + __le16 interval; + __le16 window; + __u8 own_address_type; + __u8 filter_policy; +} __packed; + +#define LE_SCAN_DISABLE 0x00 +#define LE_SCAN_ENABLE 0x01 +#define LE_SCAN_FILTER_DUP_DISABLE 0x00 +#define LE_SCAN_FILTER_DUP_ENABLE 0x01 + +#define HCI_OP_LE_SET_SCAN_ENABLE 0x200c +struct hci_cp_le_set_scan_enable { + __u8 enable; + __u8 filter_dup; +} __packed; + +#define HCI_LE_USE_PEER_ADDR 0x00 +#define HCI_LE_USE_WHITELIST 0x01 + +#define HCI_OP_LE_CREATE_CONN 0x200d +struct hci_cp_le_create_conn { + __le16 scan_interval; + __le16 scan_window; + __u8 filter_policy; + __u8 peer_addr_type; + bdaddr_t peer_addr; + __u8 own_address_type; + __le16 conn_interval_min; + __le16 conn_interval_max; + __le16 conn_latency; + __le16 supervision_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_CREATE_CONN_CANCEL 0x200e + +#define HCI_OP_LE_READ_WHITE_LIST_SIZE 0x200f +struct hci_rp_le_read_white_list_size { + __u8 status; + __u8 size; +} __packed; + +#define HCI_OP_LE_CLEAR_WHITE_LIST 0x2010 + +#define HCI_OP_LE_ADD_TO_WHITE_LIST 0x2011 +struct hci_cp_le_add_to_white_list { + __u8 bdaddr_type; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_LE_DEL_FROM_WHITE_LIST 0x2012 +struct hci_cp_le_del_from_white_list { + __u8 bdaddr_type; + bdaddr_t bdaddr; +} __packed; + +#define HCI_OP_LE_CONN_UPDATE 0x2013 +struct hci_cp_le_conn_update { + __le16 handle; + __le16 conn_interval_min; + __le16 conn_interval_max; + __le16 conn_latency; + __le16 supervision_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_READ_REMOTE_FEATURES 0x2016 +struct hci_cp_le_read_remote_features { + __le16 handle; +} __packed; + +#define HCI_OP_LE_START_ENC 0x2019 +struct hci_cp_le_start_enc { + __le16 handle; + __le64 rand; + __le16 ediv; + __u8 ltk[16]; +} __packed; + +#define HCI_OP_LE_LTK_REPLY 0x201a +struct hci_cp_le_ltk_reply { + __le16 handle; + __u8 ltk[16]; +} __packed; +struct hci_rp_le_ltk_reply { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_OP_LE_LTK_NEG_REPLY 0x201b +struct hci_cp_le_ltk_neg_reply { + __le16 handle; +} __packed; +struct hci_rp_le_ltk_neg_reply { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_OP_LE_READ_SUPPORTED_STATES 0x201c +struct hci_rp_le_read_supported_states { + __u8 status; + __u8 le_states[8]; +} __packed; + +#define HCI_OP_LE_CONN_PARAM_REQ_REPLY 0x2020 +struct hci_cp_le_conn_param_req_reply { + __le16 handle; + __le16 interval_min; + __le16 interval_max; + __le16 latency; + __le16 timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_CONN_PARAM_REQ_NEG_REPLY 0x2021 +struct hci_cp_le_conn_param_req_neg_reply { + __le16 handle; + __u8 reason; +} __packed; + +#define HCI_OP_LE_SET_DATA_LEN 0x2022 +struct hci_cp_le_set_data_len { + __le16 handle; + __le16 tx_len; + __le16 tx_time; +} __packed; +struct hci_rp_le_set_data_len { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_OP_LE_READ_DEF_DATA_LEN 0x2023 +struct hci_rp_le_read_def_data_len { + __u8 status; + __le16 tx_len; + __le16 tx_time; +} __packed; + +#define HCI_OP_LE_WRITE_DEF_DATA_LEN 0x2024 +struct hci_cp_le_write_def_data_len { + __le16 tx_len; + __le16 tx_time; +} __packed; + +#define HCI_OP_LE_CLEAR_RESOLV_LIST 0x2029 + +#define HCI_OP_LE_READ_RESOLV_LIST_SIZE 0x202a +struct hci_rp_le_read_resolv_list_size { + __u8 status; + __u8 size; +} __packed; + +#define HCI_OP_LE_SET_ADDR_RESOLV_ENABLE 0x202d + +#define HCI_OP_LE_READ_MAX_DATA_LEN 0x202f +struct hci_rp_le_read_max_data_len { + __u8 status; + __le16 tx_len; + __le16 tx_time; + __le16 rx_len; + __le16 rx_time; +} __packed; + +#define HCI_OP_LE_SET_DEFAULT_PHY 0x2031 +struct hci_cp_le_set_default_phy { + __u8 all_phys; + __u8 tx_phys; + __u8 rx_phys; +} __packed; + +#define HCI_LE_SET_PHY_1M 0x01 +#define HCI_LE_SET_PHY_2M 0x02 +#define HCI_LE_SET_PHY_CODED 0x04 + +#define HCI_OP_LE_SET_EXT_SCAN_PARAMS 0x2041 +struct hci_cp_le_set_ext_scan_params { + __u8 own_addr_type; + __u8 filter_policy; + __u8 scanning_phys; + __u8 data[0]; +} __packed; + +#define LE_SCAN_PHY_1M 0x01 +#define LE_SCAN_PHY_2M 0x02 +#define LE_SCAN_PHY_CODED 0x04 + +struct hci_cp_le_scan_phy_params { + __u8 type; + __le16 interval; + __le16 window; +} __packed; + +#define HCI_OP_LE_SET_EXT_SCAN_ENABLE 0x2042 +struct hci_cp_le_set_ext_scan_enable { + __u8 enable; + __u8 filter_dup; + __le16 duration; + __le16 period; +} __packed; + +#define HCI_OP_LE_EXT_CREATE_CONN 0x2043 +struct hci_cp_le_ext_create_conn { + __u8 filter_policy; + __u8 own_addr_type; + __u8 peer_addr_type; + bdaddr_t peer_addr; + __u8 phys; + __u8 data[0]; +} __packed; + +struct hci_cp_le_ext_conn_param { + __le16 scan_interval; + __le16 scan_window; + __le16 conn_interval_min; + __le16 conn_interval_max; + __le16 conn_latency; + __le16 supervision_timeout; + __le16 min_ce_len; + __le16 max_ce_len; +} __packed; + +#define HCI_OP_LE_READ_NUM_SUPPORTED_ADV_SETS 0x203b +struct hci_rp_le_read_num_supported_adv_sets { + __u8 status; + __u8 num_of_sets; +} __packed; + +#define HCI_OP_LE_SET_EXT_ADV_PARAMS 0x2036 +struct hci_cp_le_set_ext_adv_params { + __u8 handle; + __le16 evt_properties; + __u8 min_interval[3]; + __u8 max_interval[3]; + __u8 channel_map; + __u8 own_addr_type; + __u8 peer_addr_type; + bdaddr_t peer_addr; + __u8 filter_policy; + __u8 tx_power; + __u8 primary_phy; + __u8 secondary_max_skip; + __u8 secondary_phy; + __u8 sid; + __u8 notif_enable; +} __packed; + +#define HCI_ADV_PHY_1M 0X01 +#define HCI_ADV_PHY_2M 0x02 +#define HCI_ADV_PHY_CODED 0x03 + +struct hci_rp_le_set_ext_adv_params { + __u8 status; + __u8 tx_power; +} __packed; + +#define HCI_OP_LE_SET_EXT_ADV_ENABLE 0x2039 +struct hci_cp_le_set_ext_adv_enable { + __u8 enable; + __u8 num_of_sets; + __u8 data[0]; +} __packed; + +struct hci_cp_ext_adv_set { + __u8 handle; + __le16 duration; + __u8 max_events; +} __packed; + +#define HCI_OP_LE_SET_EXT_ADV_DATA 0x2037 +struct hci_cp_le_set_ext_adv_data { + __u8 handle; + __u8 operation; + __u8 frag_pref; + __u8 length; + __u8 data[HCI_MAX_AD_LENGTH]; +} __packed; + +#define HCI_OP_LE_SET_EXT_SCAN_RSP_DATA 0x2038 +struct hci_cp_le_set_ext_scan_rsp_data { + __u8 handle; + __u8 operation; + __u8 frag_pref; + __u8 length; + __u8 data[HCI_MAX_AD_LENGTH]; +} __packed; + +#define LE_SET_ADV_DATA_OP_COMPLETE 0x03 + +#define LE_SET_ADV_DATA_NO_FRAG 0x01 + +#define HCI_OP_LE_CLEAR_ADV_SETS 0x203d + +#define HCI_OP_LE_SET_ADV_SET_RAND_ADDR 0x2035 +struct hci_cp_le_set_adv_set_rand_addr { + __u8 handle; + bdaddr_t bdaddr; +} __packed; + +/* ---- HCI Events ---- */ +#define HCI_EV_INQUIRY_COMPLETE 0x01 + +#define HCI_EV_INQUIRY_RESULT 0x02 +struct inquiry_info { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 pscan_mode; + __u8 dev_class[3]; + __le16 clock_offset; +} __packed; + +#define HCI_EV_CONN_COMPLETE 0x03 +struct hci_ev_conn_complete { + __u8 status; + __le16 handle; + bdaddr_t bdaddr; + __u8 link_type; + __u8 encr_mode; +} __packed; + +#define HCI_EV_CONN_REQUEST 0x04 +struct hci_ev_conn_request { + bdaddr_t bdaddr; + __u8 dev_class[3]; + __u8 link_type; +} __packed; + +#define HCI_EV_DISCONN_COMPLETE 0x05 +struct hci_ev_disconn_complete { + __u8 status; + __le16 handle; + __u8 reason; +} __packed; + +#define HCI_EV_AUTH_COMPLETE 0x06 +struct hci_ev_auth_complete { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_EV_REMOTE_NAME 0x07 +struct hci_ev_remote_name { + __u8 status; + bdaddr_t bdaddr; + __u8 name[HCI_MAX_NAME_LENGTH]; +} __packed; + +#define HCI_EV_ENCRYPT_CHANGE 0x08 +struct hci_ev_encrypt_change { + __u8 status; + __le16 handle; + __u8 encrypt; +} __packed; + +#define HCI_EV_CHANGE_LINK_KEY_COMPLETE 0x09 +struct hci_ev_change_link_key_complete { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_EV_REMOTE_FEATURES 0x0b +struct hci_ev_remote_features { + __u8 status; + __le16 handle; + __u8 features[8]; +} __packed; + +#define HCI_EV_REMOTE_VERSION 0x0c +struct hci_ev_remote_version { + __u8 status; + __le16 handle; + __u8 lmp_ver; + __le16 manufacturer; + __le16 lmp_subver; +} __packed; + +#define HCI_EV_QOS_SETUP_COMPLETE 0x0d +struct hci_qos { + __u8 service_type; + __u32 token_rate; + __u32 peak_bandwidth; + __u32 latency; + __u32 delay_variation; +} __packed; +struct hci_ev_qos_setup_complete { + __u8 status; + __le16 handle; + struct hci_qos qos; +} __packed; + +#define HCI_EV_CMD_COMPLETE 0x0e +struct hci_ev_cmd_complete { + __u8 ncmd; + __le16 opcode; +} __packed; + +#define HCI_EV_CMD_STATUS 0x0f +struct hci_ev_cmd_status { + __u8 status; + __u8 ncmd; + __le16 opcode; +} __packed; + +#define HCI_EV_HARDWARE_ERROR 0x10 +struct hci_ev_hardware_error { + __u8 code; +} __packed; + +#define HCI_EV_ROLE_CHANGE 0x12 +struct hci_ev_role_change { + __u8 status; + bdaddr_t bdaddr; + __u8 role; +} __packed; + +#define HCI_EV_NUM_COMP_PKTS 0x13 +struct hci_comp_pkts_info { + __le16 handle; + __le16 count; +} __packed; + +struct hci_ev_num_comp_pkts { + __u8 num_hndl; + struct hci_comp_pkts_info handles[0]; +} __packed; + +#define HCI_EV_MODE_CHANGE 0x14 +struct hci_ev_mode_change { + __u8 status; + __le16 handle; + __u8 mode; + __le16 interval; +} __packed; + +#define HCI_EV_PIN_CODE_REQ 0x16 +struct hci_ev_pin_code_req { + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_LINK_KEY_REQ 0x17 +struct hci_ev_link_key_req { + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_LINK_KEY_NOTIFY 0x18 +struct hci_ev_link_key_notify { + bdaddr_t bdaddr; + __u8 link_key[HCI_LINK_KEY_SIZE]; + __u8 key_type; +} __packed; + +#define HCI_EV_CLOCK_OFFSET 0x1c +struct hci_ev_clock_offset { + __u8 status; + __le16 handle; + __le16 clock_offset; +} __packed; + +#define HCI_EV_PKT_TYPE_CHANGE 0x1d +struct hci_ev_pkt_type_change { + __u8 status; + __le16 handle; + __le16 pkt_type; +} __packed; + +#define HCI_EV_PSCAN_REP_MODE 0x20 +struct hci_ev_pscan_rep_mode { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; +} __packed; + +#define HCI_EV_INQUIRY_RESULT_WITH_RSSI 0x22 +struct inquiry_info_with_rssi { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 dev_class[3]; + __le16 clock_offset; + __s8 rssi; +} __packed; +struct inquiry_info_with_rssi_and_pscan_mode { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 pscan_mode; + __u8 dev_class[3]; + __le16 clock_offset; + __s8 rssi; +} __packed; + +#define HCI_EV_REMOTE_EXT_FEATURES 0x23 +struct hci_ev_remote_ext_features { + __u8 status; + __le16 handle; + __u8 page; + __u8 max_page; + __u8 features[8]; +} __packed; + +#define HCI_EV_SYNC_CONN_COMPLETE 0x2c +struct hci_ev_sync_conn_complete { + __u8 status; + __le16 handle; + bdaddr_t bdaddr; + __u8 link_type; + __u8 tx_interval; + __u8 retrans_window; + __le16 rx_pkt_len; + __le16 tx_pkt_len; + __u8 air_mode; +} __packed; + +#define HCI_EV_SYNC_CONN_CHANGED 0x2d +struct hci_ev_sync_conn_changed { + __u8 status; + __le16 handle; + __u8 tx_interval; + __u8 retrans_window; + __le16 rx_pkt_len; + __le16 tx_pkt_len; +} __packed; + +#define HCI_EV_SNIFF_SUBRATE 0x2e +struct hci_ev_sniff_subrate { + __u8 status; + __le16 handle; + __le16 max_tx_latency; + __le16 max_rx_latency; + __le16 max_remote_timeout; + __le16 max_local_timeout; +} __packed; + +#define HCI_EV_EXTENDED_INQUIRY_RESULT 0x2f +struct extended_inquiry_info { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 dev_class[3]; + __le16 clock_offset; + __s8 rssi; + __u8 data[240]; +} __packed; + +#define HCI_EV_KEY_REFRESH_COMPLETE 0x30 +struct hci_ev_key_refresh_complete { + __u8 status; + __le16 handle; +} __packed; + +#define HCI_EV_IO_CAPA_REQUEST 0x31 +struct hci_ev_io_capa_request { + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_IO_CAPA_REPLY 0x32 +struct hci_ev_io_capa_reply { + bdaddr_t bdaddr; + __u8 capability; + __u8 oob_data; + __u8 authentication; +} __packed; + +#define HCI_EV_USER_CONFIRM_REQUEST 0x33 +struct hci_ev_user_confirm_req { + bdaddr_t bdaddr; + __le32 passkey; +} __packed; + +#define HCI_EV_USER_PASSKEY_REQUEST 0x34 +struct hci_ev_user_passkey_req { + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_REMOTE_OOB_DATA_REQUEST 0x35 +struct hci_ev_remote_oob_data_request { + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_SIMPLE_PAIR_COMPLETE 0x36 +struct hci_ev_simple_pair_complete { + __u8 status; + bdaddr_t bdaddr; +} __packed; + +#define HCI_EV_USER_PASSKEY_NOTIFY 0x3b +struct hci_ev_user_passkey_notify { + bdaddr_t bdaddr; + __le32 passkey; +} __packed; + +#define HCI_KEYPRESS_STARTED 0 +#define HCI_KEYPRESS_ENTERED 1 +#define HCI_KEYPRESS_ERASED 2 +#define HCI_KEYPRESS_CLEARED 3 +#define HCI_KEYPRESS_COMPLETED 4 + +#define HCI_EV_KEYPRESS_NOTIFY 0x3c +struct hci_ev_keypress_notify { + bdaddr_t bdaddr; + __u8 type; +} __packed; + +#define HCI_EV_REMOTE_HOST_FEATURES 0x3d +struct hci_ev_remote_host_features { + bdaddr_t bdaddr; + __u8 features[8]; +} __packed; + +#define HCI_EV_LE_META 0x3e +struct hci_ev_le_meta { + __u8 subevent; +} __packed; + +#define HCI_EV_PHY_LINK_COMPLETE 0x40 +struct hci_ev_phy_link_complete { + __u8 status; + __u8 phy_handle; +} __packed; + +#define HCI_EV_CHANNEL_SELECTED 0x41 +struct hci_ev_channel_selected { + __u8 phy_handle; +} __packed; + +#define HCI_EV_DISCONN_PHY_LINK_COMPLETE 0x42 +struct hci_ev_disconn_phy_link_complete { + __u8 status; + __u8 phy_handle; + __u8 reason; +} __packed; + +#define HCI_EV_LOGICAL_LINK_COMPLETE 0x45 +struct hci_ev_logical_link_complete { + __u8 status; + __le16 handle; + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +#define HCI_EV_DISCONN_LOGICAL_LINK_COMPLETE 0x46 +struct hci_ev_disconn_logical_link_complete { + __u8 status; + __le16 handle; + __u8 reason; +} __packed; + +#define HCI_EV_NUM_COMP_BLOCKS 0x48 +struct hci_comp_blocks_info { + __le16 handle; + __le16 pkts; + __le16 blocks; +} __packed; + +struct hci_ev_num_comp_blocks { + __le16 num_blocks; + __u8 num_hndl; + struct hci_comp_blocks_info handles[0]; +} __packed; + +#define HCI_EV_SYNC_TRAIN_COMPLETE 0x4F +struct hci_ev_sync_train_complete { + __u8 status; +} __packed; + +#define HCI_EV_SLAVE_PAGE_RESP_TIMEOUT 0x54 + +#define HCI_EV_LE_CONN_COMPLETE 0x01 +struct hci_ev_le_conn_complete { + __u8 status; + __le16 handle; + __u8 role; + __u8 bdaddr_type; + bdaddr_t bdaddr; + __le16 interval; + __le16 latency; + __le16 supervision_timeout; + __u8 clk_accurancy; +} __packed; + +/* Advertising report event types */ +#define LE_ADV_IND 0x00 +#define LE_ADV_DIRECT_IND 0x01 +#define LE_ADV_SCAN_IND 0x02 +#define LE_ADV_NONCONN_IND 0x03 +#define LE_ADV_SCAN_RSP 0x04 +#define LE_ADV_INVALID 0x05 + +/* Legacy event types in extended adv report */ +#define LE_LEGACY_ADV_IND 0x0013 +#define LE_LEGACY_ADV_DIRECT_IND 0x0015 +#define LE_LEGACY_ADV_SCAN_IND 0x0012 +#define LE_LEGACY_NONCONN_IND 0x0010 +#define LE_LEGACY_SCAN_RSP_ADV 0x001b +#define LE_LEGACY_SCAN_RSP_ADV_SCAN 0x001a + +/* Extended Advertising event types */ +#define LE_EXT_ADV_NON_CONN_IND 0x0000 +#define LE_EXT_ADV_CONN_IND 0x0001 +#define LE_EXT_ADV_SCAN_IND 0x0002 +#define LE_EXT_ADV_DIRECT_IND 0x0004 +#define LE_EXT_ADV_SCAN_RSP 0x0008 +#define LE_EXT_ADV_LEGACY_PDU 0x0010 + +#define ADDR_LE_DEV_PUBLIC 0x00 +#define ADDR_LE_DEV_RANDOM 0x01 + +#define HCI_EV_LE_ADVERTISING_REPORT 0x02 +struct hci_ev_le_advertising_info { + __u8 evt_type; + __u8 bdaddr_type; + bdaddr_t bdaddr; + __u8 length; + __u8 data[0]; +} __packed; + +#define HCI_EV_LE_CONN_UPDATE_COMPLETE 0x03 +struct hci_ev_le_conn_update_complete { + __u8 status; + __le16 handle; + __le16 interval; + __le16 latency; + __le16 supervision_timeout; +} __packed; + +#define HCI_EV_LE_REMOTE_FEAT_COMPLETE 0x04 +struct hci_ev_le_remote_feat_complete { + __u8 status; + __le16 handle; + __u8 features[8]; +} __packed; + +#define HCI_EV_LE_LTK_REQ 0x05 +struct hci_ev_le_ltk_req { + __le16 handle; + __le64 rand; + __le16 ediv; +} __packed; + +#define HCI_EV_LE_REMOTE_CONN_PARAM_REQ 0x06 +struct hci_ev_le_remote_conn_param_req { + __le16 handle; + __le16 interval_min; + __le16 interval_max; + __le16 latency; + __le16 timeout; +} __packed; + +#define HCI_EV_LE_DATA_LEN_CHANGE 0x07 +struct hci_ev_le_data_len_change { + __le16 handle; + __le16 tx_len; + __le16 tx_time; + __le16 rx_len; + __le16 rx_time; +} __packed; + +#define HCI_EV_LE_DIRECT_ADV_REPORT 0x0B +struct hci_ev_le_direct_adv_info { + __u8 evt_type; + __u8 bdaddr_type; + bdaddr_t bdaddr; + __u8 direct_addr_type; + bdaddr_t direct_addr; + __s8 rssi; +} __packed; + +#define HCI_EV_LE_EXT_ADV_REPORT 0x0d +struct hci_ev_le_ext_adv_report { + __le16 evt_type; + __u8 bdaddr_type; + bdaddr_t bdaddr; + __u8 primary_phy; + __u8 secondary_phy; + __u8 sid; + __u8 tx_power; + __s8 rssi; + __le16 interval; + __u8 direct_addr_type; + bdaddr_t direct_addr; + __u8 length; + __u8 data[0]; +} __packed; + +#define HCI_EV_LE_ENHANCED_CONN_COMPLETE 0x0a +struct hci_ev_le_enh_conn_complete { + __u8 status; + __le16 handle; + __u8 role; + __u8 bdaddr_type; + bdaddr_t bdaddr; + bdaddr_t local_rpa; + bdaddr_t peer_rpa; + __le16 interval; + __le16 latency; + __le16 supervision_timeout; + __u8 clk_accurancy; +} __packed; + +#define HCI_EV_LE_EXT_ADV_SET_TERM 0x12 +struct hci_evt_le_ext_adv_set_term { + __u8 status; + __u8 handle; + __le16 conn_handle; + __u8 num_evts; +} __packed; + +#define HCI_EV_VENDOR 0xff + +/* Internal events generated by Bluetooth stack */ +#define HCI_EV_STACK_INTERNAL 0xfd +struct hci_ev_stack_internal { + __u16 type; + __u8 data[0]; +} __packed; + +#define HCI_EV_SI_DEVICE 0x01 +struct hci_ev_si_device { + __u16 event; + __u16 dev_id; +} __packed; + +#define HCI_EV_SI_SECURITY 0x02 +struct hci_ev_si_security { + __u16 event; + __u16 proto; + __u16 subproto; + __u8 incoming; +} __packed; + +/* ---- HCI Packet structures ---- */ +#define HCI_COMMAND_HDR_SIZE 3 +#define HCI_EVENT_HDR_SIZE 2 +#define HCI_ACL_HDR_SIZE 4 +#define HCI_SCO_HDR_SIZE 3 + +struct hci_command_hdr { + __le16 opcode; /* OCF & OGF */ + __u8 plen; +} __packed; + +struct hci_event_hdr { + __u8 evt; + __u8 plen; +} __packed; + +struct hci_acl_hdr { + __le16 handle; /* Handle & Flags(PB, BC) */ + __le16 dlen; +} __packed; + +struct hci_sco_hdr { + __le16 handle; + __u8 dlen; +} __packed; + +#if 0 +static inline struct hci_event_hdr *hci_event_hdr(const struct sk_buff *skb) +{ + return (struct hci_event_hdr *) skb->data; +} + +static inline struct hci_acl_hdr *hci_acl_hdr(const struct sk_buff *skb) +{ + return (struct hci_acl_hdr *) skb->data; +} + +static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb) +{ + return (struct hci_sco_hdr *) skb->data; +} +#endif + +/* Command opcode pack/unpack */ +#define hci_opcode_pack(ogf, ocf) ((__u16) ((ocf & 0x03ff)|(ogf << 10))) +#define hci_opcode_ogf(op) (op >> 10) +#define hci_opcode_ocf(op) (op & 0x03ff) + +/* ACL handle and flags pack/unpack */ +#define hci_handle_pack(h, f) ((__u16) ((h & 0x0fff)|(f << 12))) +#define hci_handle(h) (h & 0x0fff) +#define hci_flags(h) (h >> 12) + +/** + * Information from include/net/bluetooth/hci_sock.h + */ + +/* Socket options */ +#define HCI_DATA_DIR 1 +#define HCI_FILTER 2 +#define HCI_TIME_STAMP 3 + +/* CMSG flags */ +#define HCI_CMSG_DIR 0x0001 +#define HCI_CMSG_TSTAMP 0x0002 + +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_DEV_NONE 0xffff + +#define HCI_CHANNEL_RAW 0 +#define HCI_CHANNEL_USER 1 +#define HCI_CHANNEL_MONITOR 2 +#define HCI_CHANNEL_CONTROL 3 +#define HCI_CHANNEL_LOGGING 4 + +#if 0 /* Not suitable for user call, use hci_ufilter. */ +struct hci_filter { + unsigned long type_mask; + unsigned long event_mask[2]; + __le16 opcode; +}; +#endif + +struct hci_ufilter { + __u32 type_mask; + __u32 event_mask[2]; + __le16 opcode; +}; + +#define HCI_FLT_TYPE_BITS 31 +#define HCI_FLT_EVENT_BITS 63 +#define HCI_FLT_OGF_BITS 63 +#define HCI_FLT_OCF_BITS 127 + +/* Ioctl defines */ +#define HCIDEVUP _IOW('H', 201, int) +#define HCIDEVDOWN _IOW('H', 202, int) +#define HCIDEVRESET _IOW('H', 203, int) +#define HCIDEVRESTAT _IOW('H', 204, int) + +#define HCIGETDEVLIST _IOR('H', 210, int) +#define HCIGETDEVINFO _IOR('H', 211, int) +#define HCIGETCONNLIST _IOR('H', 212, int) +#define HCIGETCONNINFO _IOR('H', 213, int) +#define HCIGETAUTHINFO _IOR('H', 215, int) + +#define HCISETRAW _IOW('H', 220, int) +#define HCISETSCAN _IOW('H', 221, int) +#define HCISETAUTH _IOW('H', 222, int) +#define HCISETENCRYPT _IOW('H', 223, int) +#define HCISETPTYPE _IOW('H', 224, int) +#define HCISETLINKPOL _IOW('H', 225, int) +#define HCISETLINKMODE _IOW('H', 226, int) +#define HCISETACLMTU _IOW('H', 227, int) +#define HCISETSCOMTU _IOW('H', 228, int) + +#define HCIBLOCKADDR _IOW('H', 230, int) +#define HCIUNBLOCKADDR _IOW('H', 231, int) + +#define HCIINQUIRY _IOR('H', 240, int) + +/* Ioctl requests structures */ +struct hci_dev_stats { + __u32 err_rx; + __u32 err_tx; + __u32 cmd_tx; + __u32 evt_rx; + __u32 acl_tx; + __u32 acl_rx; + __u32 sco_tx; + __u32 sco_rx; + __u32 byte_rx; + __u32 byte_tx; +}; + +struct hci_dev_info { + __u16 dev_id; + char name[8]; + + bdaddr_t bdaddr; + + __u32 flags; + __u8 type; + + __u8 features[8]; + + __u32 pkt_type; + __u32 link_policy; + __u32 link_mode; + + __u16 acl_mtu; + __u16 acl_pkts; + __u16 sco_mtu; + __u16 sco_pkts; + + struct hci_dev_stats stat; +}; + +struct hci_conn_info { + __u16 handle; + bdaddr_t bdaddr; + __u8 type; + __u8 out; + __u16 state; + __u32 link_mode; +}; + +struct hci_dev_req { + __u16 dev_id; + __u32 dev_opt; +}; + +struct hci_dev_list_req { + __u16 dev_num; + struct hci_dev_req dev_req[0]; /* hci_dev_req structures */ +}; + +struct hci_conn_list_req { + __u16 dev_id; + __u16 conn_num; + struct hci_conn_info conn_info[0]; +}; + +struct hci_conn_info_req { + bdaddr_t bdaddr; + __u8 type; + struct hci_conn_info conn_info[0]; +}; + +struct hci_auth_info_req { + bdaddr_t bdaddr; + __u8 type; +}; + +struct hci_inquiry_req { + __u16 dev_id; + __u16 flags; + __u8 lap[3]; + __u8 length; + __u8 num_rsp; +}; +#define IREQ_CACHE_FLUSH 0x0001 + +#endif /* HCI_IOCTL_HPP_ */ + diff --git a/api/direct_bt/HCITypes.hpp b/api/direct_bt/HCITypes.hpp new file mode 100644 index 00000000..e7cb0384 --- /dev/null +++ b/api/direct_bt/HCITypes.hpp @@ -0,0 +1,379 @@ +/* + * 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. + */ + +#ifndef HCI_TYPES_HPP_ +#define HCI_TYPES_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> + +#include "UUID.hpp" +#include "BTAddress.hpp" +#include "BTTypes.hpp" +#include "HCIComm.hpp" +#include "MgmtComm.hpp" + +#define JAVA_MAIN_PACKAGE "org/tinyb" +#define JAVA_HCI_PACKAGE "tinyb/hci" + +namespace direct_bt { + + // ************************************************* + // ************************************************* + // ************************************************* + + class HCIAdapter; // forward + class HCIDevice; // forward + + class HCISession + { + friend class HCIAdapter; // top manager: adapter open/close + friend class HCIDevice; // local device manager: device connect/disconnect + + private: + static std::atomic_int name_counter; + HCIAdapter &adapter; + HCIComm hciComm; + std::shared_ptr<HCIDevice> connectedDevice; + + HCISession(HCIAdapter &a, const uint16_t dev_id, const uint16_t channel, const int timeoutMS=HCI_TO_SEND_REQ_POLL_MS) + : adapter(a), hciComm(dev_id, channel, timeoutMS), + connectedDevice(nullptr), name(name_counter.fetch_add(1)) + {} + + void connected(std::shared_ptr<HCIDevice> device) { + connectedDevice = device; + } + + public: + const int name; + + ~HCISession() { disconnect(); close(); } + + const HCIAdapter &getAdapter() { return adapter; } + uint16_t getConnectedDeviceHandle() const { return hciComm.leConnHandle(); } + std::shared_ptr<HCIDevice> getConnectedDevice() { return connectedDevice; } + + void disconnect(const uint8_t reason=0); + + bool close(); + + bool isOpen() const { return hciComm.isOpen(); } + int dd() const { return hciComm.dd(); } + }; + + inline bool operator<(const HCISession& lhs, const HCISession& rhs) + { return lhs.name < rhs.name; } + + inline bool operator==(const HCISession& lhs, const HCISession& rhs) + { return lhs.name == rhs.name; } + + inline bool operator!=(const HCISession& lhs, const HCISession& rhs) + { return !(lhs == rhs); } + + + // ************************************************* + // ************************************************* + // ************************************************* + + class HCIObject + { + protected: + std::mutex lk; + std::atomic_bool valid; + + HCIObject() : valid(true) {} + + bool lock() { + if (valid) { + lk.lock(); + return true; + } else { + return false; + } + } + + void unlock() { + lk.unlock(); + } + + public: + bool isValid() { return valid; } + }; + + // ************************************************* + // ************************************************* + // ************************************************* + + class HCIDeviceDiscoveryListener { + public: + virtual void deviceAdded(HCIAdapter const &a, std::shared_ptr<HCIDevice> device) = 0; + virtual void deviceUpdated(HCIAdapter const &a, std::shared_ptr<HCIDevice> device) = 0; + virtual ~HCIDeviceDiscoveryListener() {} + }; + + class HCIDevice : public HCIObject + { + friend HCIAdapter; // managing us: ctor and update(..) during discovery + + private: + static const int to_connect_ms = 5000; + + HCIAdapter const & adapter; + uint64_t ts_update; + std::string name; + int8_t rssi = 0; + int8_t tx_power = 0; + std::shared_ptr<ManufactureSpecificData> msd = nullptr; + std::vector<std::shared_ptr<uuid_t>> services; + + HCIDevice(HCIAdapter const & adapter, EInfoReport const & r); + + void addService(std::shared_ptr<uuid_t> const &uuid); + void addServices(std::vector<std::shared_ptr<uuid_t>> const & services); + + void update(EInfoReport const & data); + + public: + const uint64_t ts_creation; + /** Device mac address */ + const EUI48 mac; + + ~HCIDevice(); + + /** Returns the managing adapter */ + HCIAdapter const & getAdapter() const { return adapter; } + + /** Returns the shares reference of this instance, managed by the adapter */ + std::shared_ptr<HCIDevice> getSharedInstance() const; + + uint64_t getCreationTimestamp() const { return ts_creation; } + uint64_t getUpdateTimestamp() const { return ts_update; } + uint64_t getLastUpdateAge(const uint64_t ts_now) const { return ts_now - ts_update; } + EUI48 const & getAddress() const { return mac; } + std::string getAddressString() const { return mac.toString(); } + std::string const & getName() const { return name; } + bool hasName() const { return name.length()>0; } + int8_t getRSSI() const { return rssi; } + int8_t getTxPower() const { return tx_power; } + std::shared_ptr<ManufactureSpecificData> const getManufactureSpecificData() const { return msd; } + + std::vector<std::shared_ptr<uuid_t>> getServices() const { return services; } + + /** Returns index >= 0 if found, otherwise -1 */ + int findService(std::shared_ptr<uuid_t> const &uuid) const; + + std::string toString() const; + + /** + * Creates a new connection to this device. + * <p> + * Returns the new device connection handle if successful, otherwise 0 is returned. + * </p> + * <p> + * The device connection handle as well as this device is stored and tracked by + * the given HCISession instance. + * </p> + * <p> + * Default parameter values are chosen for using public address resolution + * and usual connection latency, interval etc. + * </p> + */ + uint16_t le_connect(HCISession& s, + const uint8_t peer_mac_type=HCIADDR_LE_PUBLIC, const uint8_t own_mac_type=HCIADDR_LE_PUBLIC, + const uint16_t interval=0x0004, const uint16_t window=0x0004, + const uint16_t min_interval=0x000F, const uint16_t max_interval=0x000F, + const uint16_t latency=0x0000, const uint16_t supervision_timeout=0x0C80, + const uint16_t min_ce_length=0x0001, const uint16_t max_ce_length=0x0001, + const uint8_t initiator_filter=0); + }; + + inline bool operator<(const HCIDevice& lhs, const HCIDevice& rhs) + { return lhs.mac < rhs.mac; } + + inline bool operator==(const HCIDevice& lhs, const HCIDevice& rhs) + { return lhs.mac == rhs.mac; } + + inline bool operator!=(const HCIDevice& lhs, const HCIDevice& rhs) + { return !(lhs == rhs); } + + // ************************************************* + // ************************************************* + // ************************************************* + + class HCIAdapter : public HCIObject + { + private: + /** Returns index >= 0 if found, otherwise -1 */ + static int findDevice(std::vector<std::shared_ptr<HCIDevice>> const & devices, EUI48 const & mac); + + MgmtHandler& mgmt; + std::shared_ptr<const AdapterInfo> adapterInfo; + + std::shared_ptr<HCISession> session; + std::vector<std::shared_ptr<HCIDevice>> scannedDevices; // all devices scanned + std::vector<std::shared_ptr<HCIDevice>> discoveredDevices; // matching all requirements for export + std::shared_ptr<HCIDeviceDiscoveryListener> deviceDiscoveryListener = nullptr; + + bool validateDevInfo(); + + friend bool HCISession::close(); + void sessionClosed(HCISession& s); + + friend std::shared_ptr<HCIDevice> HCIDevice::getSharedInstance() const; + int findScannedDeviceIdx(EUI48 const & mac) const; + std::shared_ptr<HCIDevice> findScannedDevice (EUI48 const & mac) const; + bool addScannedDevice(std::shared_ptr<HCIDevice> const &device); + + bool addDiscoveredDevice(std::shared_ptr<HCIDevice> const &device); + + protected: + + public: + const int dev_id; + + /** + * Using the default adapter device + */ + HCIAdapter(); + + /** + * @param[in] mac address + */ + HCIAdapter(EUI48 &mac); + + /** + * @param[in] dev_id an already identified HCI device id + */ + HCIAdapter(const int dev_id); + + ~HCIAdapter(); + + bool hasDevId() const { return 0 <= dev_id; } + + EUI48 const & getAddress() const { return adapterInfo->mac; } + std::string getAddressString() const { return adapterInfo->mac.toString(); } + std::string const & getName() const { return adapterInfo->name; } + + /** + * Returns a reference to the newly opened session + * if successful, otherwise nullptr is returned. + */ + std::shared_ptr<HCISession> open(); + + // device discovery aka device scanning + + /** + * Replaces the HCIDeviceDiscoveryListener with the given instance, returning the replaced one. + */ + std::shared_ptr<HCIDeviceDiscoveryListener> setDeviceDiscoveryListener(std::shared_ptr<HCIDeviceDiscoveryListener> l); + + /** + * Starts a new discovery session. + * <p> + * Returns true if successful, otherwise false; + * </p> + * <p> + * Default parameter values are chosen for using public address resolution + * and usual discovery intervals etc. + * </p> + */ + bool startDiscovery(HCISession& s, uint8_t own_mac_type=HCIADDR_LE_PUBLIC, + uint16_t interval=0x0004, uint16_t window=0x0004); + + /** + * Closes the discovery session. + * @return true if no error, otherwise false. + */ + void stopDiscovery(HCISession& s); + + /** + * Discovery devices up until 'timeoutMS' in milliseconds + * or until 'waitForDeviceCount' and 'waitForDevice' + * devices matching 'ad_type_req' criteria has been + * reached. + * + * <p> + * 'waitForDeviceCount' is the number of successfully + * scanned devices matching 'ad_type_req' + * before returning if 'timeoutMS' hasn't been reached. + * <br> + * Default value is '1', i.e. wait for only one device. + * A value of <= 0 denotes infinitive, here 'timeoutMS' + * will end the discovery process. + * </p> + * + * <p> + * 'waitForDevice' is a EUI48 denoting a specific device + * to wait for. + * <br> + * Default value is 'EUI48_ANY_DEVICE', i.e. wait for any device. + * </p> + * + * <p> + * 'ad_type_req' is a bitmask of 'EInfoReport::Element' + * denoting required data to be received before + * adding or updating the devices in the discovered list. + * <br> + * Default value is: 'EInfoReport::Element::NAME', + * while 'EInfoReport::Element::BDADDR|EInfoReport::Element::RSSI' is implicit + * and guarantedd by the AD protocol. + * </p> + * + * @return number of successfully scanned devices matching above criteria + * or -1 if an error has occurred. + */ + int discoverDevices(HCISession& s, + const int waitForDeviceCount=1, + const EUI48 &waitForDevice=EUI48_ANY_DEVICE, + const int timeoutMS=HCI_TO_SEND_REQ_POLL_MS, + const uint32_t ad_type_req=static_cast<uint32_t>(EInfoReport::Element::NAME)); + + /** Returns discovered devices from a discovery */ + std::vector<std::shared_ptr<HCIDevice>> getDiscoveredDevices() { return discoveredDevices; } + + /** Discards all discovered devices. */ + void removeDiscoveredDevices(); + + /** Returns index >= 0 if found, otherwise -1 */ + int findDiscoveredDeviceIdx(EUI48 const & mac) const; + + /** Returns shared HCIDevice if found, otherwise nullptr */ + std::shared_ptr<HCIDevice> findDiscoveredDevice (EUI48 const & mac) const; + + std::shared_ptr<HCIDevice> getDiscoveredDevice(int index) const { return discoveredDevices.at(index); } + + std::string toString() const; + }; + +} // namespace direct_bt + +#endif /* HCITYPES_HPP_ */ diff --git a/api/direct_bt/L2CAPComm.hpp b/api/direct_bt/L2CAPComm.hpp new file mode 100644 index 00000000..152ac58d --- /dev/null +++ b/api/direct_bt/L2CAPComm.hpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#ifndef L2CAP_COMM_HPP_ +#define L2CAP_COMM_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> + +#include "UUID.hpp" +#include "BTTypes.hpp" +#include "HCITypes.hpp" + +namespace direct_bt { + + class L2CAPComm { + public: + enum State : int { + Error = -1, + Disconnected = 0, + Connecting = 1, + Connected = 2, + }; + + static std::string getStateString(const State state); + + private: + static int l2cap_open_dev(const EUI48 & adapterAddress, const uint16_t psm, const uint16_t cid, const bool pubaddr, const bool blocking); + static int l2cap_close_dev(int dd); + + State state; + std::shared_ptr<HCIDevice> device = nullptr; + const uint16_t psm; + const uint16_t cid; + const bool pubaddr; + const bool blocking; + int _dd; // the l2cap socket + volatile bool interruptReadFlag; // for forced disconnect + + public: + L2CAPComm(std::shared_ptr<HCIDevice> device, const uint16_t psm, const uint16_t cid, const bool pubaddr=true, const bool blocking=true) + : state(Disconnected), device(device), psm(psm), cid(cid), pubaddr(pubaddr), blocking(blocking), _dd(-1), interruptReadFlag(false) {} + ~L2CAPComm() { disconnect(); } + + std::shared_ptr<HCIDevice> getDevice() { return device; } + + State getState() const { return state; } + std::string getStateString() const { return getStateString(state); } + + /** BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ */ + State connect(); + + bool disconnect(); + + bool isOpen() const { return 0 <= _dd; } + int dd() const { return _dd; } + + int read(uint8_t* buffer, const int capacity, const int timeoutMS); + int write(const uint8_t *buffer, const int length); + }; + +} // namespace direct_bt + +#endif /* L2CAP_COMM_HPP_ */ diff --git a/api/direct_bt/L2CAPIoctl.hpp b/api/direct_bt/L2CAPIoctl.hpp new file mode 100644 index 00000000..8948c3d0 --- /dev/null +++ b/api/direct_bt/L2CAPIoctl.hpp @@ -0,0 +1,515 @@ +/* + * 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. + * + * **************************************************************************************** + * **************************************************************************************** + * **************************************************************************************** + * + * This file includes certain information from Linux Kernel's BlueZ protocol stack, + * allowing the use of these kernel services via system calls. + * Therefore, the license has been aligned with this project. + * See file COPYING in the root folder of this project for more details, + * as well as Linus Torvalds's Linux Kernel license exception regarding kernel syscalls (ioctl): + * <https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note>. + * + * Original sources: + * linux-kernel 4.19 include/net/bluetooth/l2cap.h (git head d8edd9ed156a1a840f1b1c2dbbf458684d6eea6e). + * + * Original copyright: + * BlueZ - Bluetooth protocol stack for Linux + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2009-2010 Gustavo F. Padovan <[email protected]> + * Copyright (C) 2010 Google Inc. + */ + +#ifndef L2CAP_IOCTL_HPP_ +#define L2CAP_IOCTL_HPP_ + +#include "BTAddress.hpp" + +#include "linux_kernel_types.hpp" + +/** + * BT Core Spec v5.2: Vol 3, Part A: BT Logical Link Control and Adaption Protocol (L2CAP) + */ + +extern "C" { + #include <stdint.h> + #include <sys/socket.h> +} /* extern "C" */ + +/** + * Information from include/net/bluetooth/l2cap.h + * Mixed with own comments. + */ + +/* L2CAP defaults */ +#define L2CAP_DEFAULT_MTU 672 +#define L2CAP_DEFAULT_MIN_MTU 48 +#define L2CAP_DEFAULT_FLUSH_TO 0xFFFF +#define L2CAP_EFS_DEFAULT_FLUSH_TO 0xFFFFFFFF +#define L2CAP_DEFAULT_TX_WINDOW 63 +#define L2CAP_DEFAULT_EXT_WINDOW 0x3FFF +#define L2CAP_DEFAULT_MAX_TX 3 +#define L2CAP_DEFAULT_RETRANS_TO 2000 /* 2 seconds */ +#define L2CAP_DEFAULT_MONITOR_TO 12000 /* 12 seconds */ +#define L2CAP_DEFAULT_MAX_PDU_SIZE 1492 /* Sized for AMP packet */ +#define L2CAP_DEFAULT_ACK_TO 200 +#define L2CAP_DEFAULT_MAX_SDU_SIZE 0xFFFF +#define L2CAP_DEFAULT_SDU_ITIME 0xFFFFFFFF +#define L2CAP_DEFAULT_ACC_LAT 0xFFFFFFFF +#define L2CAP_BREDR_MAX_PAYLOAD 1019 /* 3-DH5 packet */ +#define L2CAP_LE_MIN_MTU 23 + +/** + * L2CAP socket address + * <p> + * BT Core Spec v5.2: Vol 3, Part A: L2CAP_CONNECTION_REQ + * </p> + */ +struct sockaddr_l2 { + sa_family_t l2_family; + /** Protocol Service Multiplexers */ + __le16 l2_psm; + bdaddr_t l2_bdaddr; + /** Channel ID */ + __le16 l2_cid; + __u8 l2_bdaddr_type; +}; + + +/* L2CAP socket options */ +#define L2CAP_OPTIONS 0x01 +struct l2cap_options { + __u16 omtu; + __u16 imtu; + __u16 flush_to; + __u8 mode; + __u8 fcs; + __u8 max_tx; + __u16 txwin_size; +}; + +#define L2CAP_CONNINFO 0x02 +struct l2cap_conninfo { + __u16 hci_handle; + __u8 dev_class[3]; +}; + +#define L2CAP_LM 0x03 +#define L2CAP_LM_MASTER 0x0001 +#define L2CAP_LM_AUTH 0x0002 +#define L2CAP_LM_ENCRYPT 0x0004 +#define L2CAP_LM_TRUSTED 0x0008 +#define L2CAP_LM_RELIABLE 0x0010 +#define L2CAP_LM_SECURE 0x0020 +#define L2CAP_LM_FIPS 0x0040 + +/* L2CAP command codes */ +#define L2CAP_COMMAND_REJ 0x01 +#define L2CAP_CONN_REQ 0x02 +#define L2CAP_CONN_RSP 0x03 +#define L2CAP_CONF_REQ 0x04 +#define L2CAP_CONF_RSP 0x05 +#define L2CAP_DISCONN_REQ 0x06 +#define L2CAP_DISCONN_RSP 0x07 +#define L2CAP_ECHO_REQ 0x08 +#define L2CAP_ECHO_RSP 0x09 +#define L2CAP_INFO_REQ 0x0a +#define L2CAP_INFO_RSP 0x0b +#define L2CAP_CREATE_CHAN_REQ 0x0c +#define L2CAP_CREATE_CHAN_RSP 0x0d +#define L2CAP_MOVE_CHAN_REQ 0x0e +#define L2CAP_MOVE_CHAN_RSP 0x0f +#define L2CAP_MOVE_CHAN_CFM 0x10 +#define L2CAP_MOVE_CHAN_CFM_RSP 0x11 +#define L2CAP_CONN_PARAM_UPDATE_REQ 0x12 +#define L2CAP_CONN_PARAM_UPDATE_RSP 0x13 +#define L2CAP_LE_CONN_REQ 0x14 +#define L2CAP_LE_CONN_RSP 0x15 +#define L2CAP_LE_CREDITS 0x16 + +/* L2CAP extended feature mask */ +#define L2CAP_FEAT_FLOWCTL 0x00000001 +#define L2CAP_FEAT_RETRANS 0x00000002 +#define L2CAP_FEAT_BIDIR_QOS 0x00000004 +#define L2CAP_FEAT_ERTM 0x00000008 +#define L2CAP_FEAT_STREAMING 0x00000010 +#define L2CAP_FEAT_FCS 0x00000020 +#define L2CAP_FEAT_EXT_FLOW 0x00000040 +#define L2CAP_FEAT_FIXED_CHAN 0x00000080 +#define L2CAP_FEAT_EXT_WINDOW 0x00000100 +#define L2CAP_FEAT_UCD 0x00000200 + +/* L2CAP checksum option */ +#define L2CAP_FCS_NONE 0x00 +#define L2CAP_FCS_CRC16 0x01 + +/* L2CAP fixed channels */ +#define L2CAP_FC_SIG_BREDR 0x02 +#define L2CAP_FC_CONNLESS 0x04 +#define L2CAP_FC_A2MP 0x08 +#define L2CAP_FC_ATT 0x10 +#define L2CAP_FC_SIG_LE 0x20 +#define L2CAP_FC_SMP_LE 0x40 +#define L2CAP_FC_SMP_BREDR 0x80 + +/* L2CAP Control Field bit masks */ +#define L2CAP_CTRL_SAR 0xC000 +#define L2CAP_CTRL_REQSEQ 0x3F00 +#define L2CAP_CTRL_TXSEQ 0x007E +#define L2CAP_CTRL_SUPERVISE 0x000C + +#define L2CAP_CTRL_RETRANS 0x0080 +#define L2CAP_CTRL_FINAL 0x0080 +#define L2CAP_CTRL_POLL 0x0010 +#define L2CAP_CTRL_FRAME_TYPE 0x0001 /* I- or S-Frame */ + +#define L2CAP_CTRL_TXSEQ_SHIFT 1 +#define L2CAP_CTRL_SUPER_SHIFT 2 +#define L2CAP_CTRL_POLL_SHIFT 4 +#define L2CAP_CTRL_FINAL_SHIFT 7 +#define L2CAP_CTRL_REQSEQ_SHIFT 8 +#define L2CAP_CTRL_SAR_SHIFT 14 + +/* L2CAP Extended Control Field bit mask */ +#define L2CAP_EXT_CTRL_TXSEQ 0xFFFC0000 +#define L2CAP_EXT_CTRL_SAR 0x00030000 +#define L2CAP_EXT_CTRL_SUPERVISE 0x00030000 +#define L2CAP_EXT_CTRL_REQSEQ 0x0000FFFC + +#define L2CAP_EXT_CTRL_POLL 0x00040000 +#define L2CAP_EXT_CTRL_FINAL 0x00000002 +#define L2CAP_EXT_CTRL_FRAME_TYPE 0x00000001 /* I- or S-Frame */ + +#define L2CAP_EXT_CTRL_FINAL_SHIFT 1 +#define L2CAP_EXT_CTRL_REQSEQ_SHIFT 2 +#define L2CAP_EXT_CTRL_SAR_SHIFT 16 +#define L2CAP_EXT_CTRL_SUPER_SHIFT 16 +#define L2CAP_EXT_CTRL_POLL_SHIFT 18 +#define L2CAP_EXT_CTRL_TXSEQ_SHIFT 18 + +/* L2CAP Supervisory Function */ +#define L2CAP_SUPER_RR 0x00 +#define L2CAP_SUPER_REJ 0x01 +#define L2CAP_SUPER_RNR 0x02 +#define L2CAP_SUPER_SREJ 0x03 + +/* L2CAP Segmentation and Reassembly */ +#define L2CAP_SAR_UNSEGMENTED 0x00 +#define L2CAP_SAR_START 0x01 +#define L2CAP_SAR_END 0x02 +#define L2CAP_SAR_CONTINUE 0x03 + +/* L2CAP Command rej. reasons */ +#define L2CAP_REJ_NOT_UNDERSTOOD 0x0000 +#define L2CAP_REJ_MTU_EXCEEDED 0x0001 +#define L2CAP_REJ_INVALID_CID 0x0002 + +/* L2CAP structures */ +struct l2cap_hdr { + __le16 len; + __le16 cid; +} __packed; +#define L2CAP_HDR_SIZE 4 +#define L2CAP_ENH_HDR_SIZE 6 +#define L2CAP_EXT_HDR_SIZE 8 + +#define L2CAP_FCS_SIZE 2 +#define L2CAP_SDULEN_SIZE 2 +#define L2CAP_PSMLEN_SIZE 2 +#define L2CAP_ENH_CTRL_SIZE 2 +#define L2CAP_EXT_CTRL_SIZE 4 + +struct l2cap_cmd_hdr { + __u8 code; + __u8 ident; + __le16 len; +} __packed; +#define L2CAP_CMD_HDR_SIZE 4 + +struct l2cap_cmd_rej_unk { + __le16 reason; +} __packed; + +struct l2cap_cmd_rej_mtu { + __le16 reason; + __le16 max_mtu; +} __packed; + +struct l2cap_cmd_rej_cid { + __le16 reason; + __le16 scid; + __le16 dcid; +} __packed; + +struct l2cap_conn_req { + __le16 psm; + __le16 scid; +} __packed; + +struct l2cap_conn_rsp { + __le16 dcid; + __le16 scid; + __le16 result; + __le16 status; +} __packed; + +/* protocol/service multiplexer (PSM) */ +#if 0 /* BTIoctl.hpp */ +#define L2CAP_PSM_SDP 0x0001 +#define L2CAP_PSM_RFCOMM 0x0003 +#define L2CAP_PSM_3DSP 0x0021 +#define L2CAP_PSM_IPSP 0x0023 /* 6LoWPAN */ + +#define L2CAP_PSM_DYN_START 0x1001 +#define L2CAP_PSM_DYN_END 0xffff +#define L2CAP_PSM_AUTO_END 0x10ff +#define L2CAP_PSM_LE_DYN_START 0x0080 +#define L2CAP_PSM_LE_DYN_END 0x00ff + + +/* channel identifier */ +#define L2CAP_CID_SIGNALING 0x0001 +#define L2CAP_CID_CONN_LESS 0x0002 +#define L2CAP_CID_A2MP 0x0003 +#define L2CAP_CID_ATT 0x0004 +#define L2CAP_CID_LE_SIGNALING 0x0005 +#define L2CAP_CID_SMP 0x0006 +#define L2CAP_CID_SMP_BREDR 0x0007 +#define L2CAP_CID_DYN_START 0x0040 +#define L2CAP_CID_DYN_END 0xffff +#define L2CAP_CID_LE_DYN_END 0x007f +#endif /* BTIoctl.hpp */ + +/* connect/create channel results */ +#define L2CAP_CR_SUCCESS 0x0000 +#define L2CAP_CR_PEND 0x0001 +#define L2CAP_CR_BAD_PSM 0x0002 +#define L2CAP_CR_SEC_BLOCK 0x0003 +#define L2CAP_CR_NO_MEM 0x0004 +#define L2CAP_CR_BAD_AMP 0x0005 +#define L2CAP_CR_AUTHENTICATION 0x0005 +#define L2CAP_CR_AUTHORIZATION 0x0006 +#define L2CAP_CR_BAD_KEY_SIZE 0x0007 +#define L2CAP_CR_ENCRYPTION 0x0008 +#define L2CAP_CR_INVALID_SCID 0x0009 +#define L2CAP_CR_SCID_IN_USE 0x000A + +/* connect/create channel status */ +#define L2CAP_CS_NO_INFO 0x0000 +#define L2CAP_CS_AUTHEN_PEND 0x0001 +#define L2CAP_CS_AUTHOR_PEND 0x0002 + +struct l2cap_conf_req { + __le16 dcid; + __le16 flags; + __u8 data[0]; +} __packed; + +struct l2cap_conf_rsp { + __le16 scid; + __le16 flags; + __le16 result; + __u8 data[0]; +} __packed; + +#define L2CAP_CONF_SUCCESS 0x0000 +#define L2CAP_CONF_UNACCEPT 0x0001 +#define L2CAP_CONF_REJECT 0x0002 +#define L2CAP_CONF_UNKNOWN 0x0003 +#define L2CAP_CONF_PENDING 0x0004 +#define L2CAP_CONF_EFS_REJECT 0x0005 + +/* configuration req/rsp continuation flag */ +#define L2CAP_CONF_FLAG_CONTINUATION 0x0001 + +struct l2cap_conf_opt { + __u8 type; + __u8 len; + __u8 val[0]; +} __packed; +#define L2CAP_CONF_OPT_SIZE 2 + +#define L2CAP_CONF_HINT 0x80 +#define L2CAP_CONF_MASK 0x7f + +#define L2CAP_CONF_MTU 0x01 +#define L2CAP_CONF_FLUSH_TO 0x02 +#define L2CAP_CONF_QOS 0x03 +#define L2CAP_CONF_RFC 0x04 +#define L2CAP_CONF_FCS 0x05 +#define L2CAP_CONF_EFS 0x06 +#define L2CAP_CONF_EWS 0x07 + +#define L2CAP_CONF_MAX_SIZE 22 + +struct l2cap_conf_rfc { + __u8 mode; + __u8 txwin_size; + __u8 max_transmit; + __le16 retrans_timeout; + __le16 monitor_timeout; + __le16 max_pdu_size; +} __packed; + +#define L2CAP_MODE_BASIC 0x00 +#define L2CAP_MODE_RETRANS 0x01 +#define L2CAP_MODE_FLOWCTL 0x02 +#define L2CAP_MODE_ERTM 0x03 +#define L2CAP_MODE_STREAMING 0x04 + +/* Unlike the above this one doesn't actually map to anything that would + * ever be sent over the air. Therefore, use a value that's unlikely to + * ever be used in the BR/EDR configuration phase. + */ +#define L2CAP_MODE_LE_FLOWCTL 0x80 + +struct l2cap_conf_efs { + __u8 id; + __u8 stype; + __le16 msdu; + __le32 sdu_itime; + __le32 acc_lat; + __le32 flush_to; +} __packed; + +#define L2CAP_SERV_NOTRAFIC 0x00 +#define L2CAP_SERV_BESTEFFORT 0x01 +#define L2CAP_SERV_GUARANTEED 0x02 + +#define L2CAP_BESTEFFORT_ID 0x01 + +struct l2cap_disconn_req { + __le16 dcid; + __le16 scid; +} __packed; + +struct l2cap_disconn_rsp { + __le16 dcid; + __le16 scid; +} __packed; + +struct l2cap_info_req { + __le16 type; +} __packed; + +struct l2cap_info_rsp { + __le16 type; + __le16 result; + __u8 data[0]; +} __packed; + +struct l2cap_create_chan_req { + __le16 psm; + __le16 scid; + __u8 amp_id; +} __packed; + +struct l2cap_create_chan_rsp { + __le16 dcid; + __le16 scid; + __le16 result; + __le16 status; +} __packed; + +struct l2cap_move_chan_req { + __le16 icid; + __u8 dest_amp_id; +} __packed; + +struct l2cap_move_chan_rsp { + __le16 icid; + __le16 result; +} __packed; + +#define L2CAP_MR_SUCCESS 0x0000 +#define L2CAP_MR_PEND 0x0001 +#define L2CAP_MR_BAD_ID 0x0002 +#define L2CAP_MR_SAME_ID 0x0003 +#define L2CAP_MR_NOT_SUPP 0x0004 +#define L2CAP_MR_COLLISION 0x0005 +#define L2CAP_MR_NOT_ALLOWED 0x0006 + +struct l2cap_move_chan_cfm { + __le16 icid; + __le16 result; +} __packed; + +#define L2CAP_MC_CONFIRMED 0x0000 +#define L2CAP_MC_UNCONFIRMED 0x0001 + +struct l2cap_move_chan_cfm_rsp { + __le16 icid; +} __packed; + +/* info type */ +#define L2CAP_IT_CL_MTU 0x0001 +#define L2CAP_IT_FEAT_MASK 0x0002 +#define L2CAP_IT_FIXED_CHAN 0x0003 + +/* info result */ +#define L2CAP_IR_SUCCESS 0x0000 +#define L2CAP_IR_NOTSUPP 0x0001 + +struct l2cap_conn_param_update_req { + __le16 min; + __le16 max; + __le16 latency; + __le16 to_multiplier; +} __packed; + +struct l2cap_conn_param_update_rsp { + __le16 result; +} __packed; + +/* Connection Parameters result */ +#define L2CAP_CONN_PARAM_ACCEPTED 0x0000 +#define L2CAP_CONN_PARAM_REJECTED 0x0001 + +#define L2CAP_LE_MAX_CREDITS 10 +#define L2CAP_LE_DEFAULT_MPS 230 + +struct l2cap_le_conn_req { + __le16 psm; + __le16 scid; + __le16 mtu; + __le16 mps; + __le16 credits; +} __packed; + +struct l2cap_le_conn_rsp { + __le16 dcid; + __le16 mtu; + __le16 mps; + __le16 credits; + __le16 result; +} __packed; + +struct l2cap_le_credits { + __le16 cid; + __le16 credits; +} __packed; + +#endif /* L2CAP_IOCTL_HPP_ */ diff --git a/api/direct_bt/LFRingbuffer.hpp b/api/direct_bt/LFRingbuffer.hpp new file mode 100644 index 00000000..44ac514a --- /dev/null +++ b/api/direct_bt/LFRingbuffer.hpp @@ -0,0 +1,424 @@ +/* + * 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. + */ + +#ifndef LFRINGBUFFER_HPP_ +#define LFRINGBUFFER_HPP_ + +#include <cstring> +#include <string> +#include <cstdint> +#include <atomic> +#include <memory> +#include <mutex> +#include <condition_variable> + +#include "BasicTypes.hpp" + +#include "Ringbuffer.hpp" + +namespace direct_bt { + + +/** + * Simple implementation of {@link Ringbuffer}, + * exposing <i>lock-free</i> + * {@link #get() get*(..)} and {@link #put(Object) put*(..)} methods. + * <p> + * Implementation utilizes the <i>Always Keep One Slot Open</i>, + * hence implementation maintains an internal array of <code>capacity</code> <i>plus one</i>! + * </p> + * <p> + * Implementation is thread safe if: + * <ul> + * <li>{@link #get() get*(..)} operations from multiple threads.</li> + * <li>{@link #put(Object) put*(..)} operations from multiple threads.</li> + * <li>{@link #get() get*(..)} and {@link #put(Object) put*(..)} thread may be the same.</li> + * </ul> + * </p> + * <p> + * Following methods use acquire the global multi-read and -write mutex: + * <ul> + * <li>{@link #resetFull(Object[])}</li> + * <li>{@link #clear()}</li> + * <li>{@link #growEmptyBuffer(Object[])}</li> + * </ul> + * </p> + * <p> + * Characteristics: + * <ul> + * <li>Read position points to the last read element.</li> + * <li>Write position points to the last written element.</li> + * </ul> + * <table border="1"> + * <tr><td>Empty</td><td>writePos == readPos</td><td>size == 0</td></tr> + * <tr><td>Full</td><td>writePos == readPos - 1</td><td>size == capacity</td></tr> + * </table> + * </p> + */ +template <typename T, std::nullptr_t nullelem> class LFRingbuffer : public Ringbuffer<T> { + private: + std::mutex syncRead, syncMultiRead; + std::mutex syncWrite, syncMultiWrite; + std::condition_variable cvRead; + std::condition_variable cvWrite; + + /* final */ int volatile capacityPlusOne; // not final due to grow + /* final */ T * volatile array; // not final due to grow + int volatile readPos; + int volatile writePos; + std::atomic_int size; + + T * newArray(const int count) { + return new T[count]; + } + void freeArray(T * a) { + delete[] a; + } + + void cloneFrom(const bool allocArrayAndCapacity, const LFRingbuffer & source) { + if( allocArrayAndCapacity ) { + capacityPlusOne = source.capacityPlusOne; + if( nullptr != array ) { + freeArray(array, true); + } + array = newArray(capacityPlusOne); + } else if( capacityPlusOne != source.capacityPlusOne ) { + throw InternalError("capacityPlusOne not equal: this "+toString()+", source "+source.toString(), E_FILE_LINE); + } + + readPos = source.readPos; + writePos = source.writePos; + size = source.size; + int localWritePos = readPos; + for(int i=0; i<size; i++) { + localWritePos = (localWritePos + 1) % capacityPlusOne; + array[localWritePos] = source.array[localWritePos]; + } + if( writePos != localWritePos ) { + throw InternalError("copy segment error: this "+toString()+", localWritePos "+std::to_string(localWritePos)+"; source "+source.toString(), E_FILE_LINE); + } + } + + void resetImpl(const T * copyFrom, const int copyFromCount) /* throws IllegalArgumentException */ { + // clear all elements, zero size + if( 0 < size ) { + int localReadPos = readPos; + for(int i=0; i<size; i++) { + localReadPos = (localReadPos + 1) % capacityPlusOne; + array[localReadPos] = nullelem; + } + if( writePos != localReadPos ) { + throw InternalError("copy segment error: this "+toString()+", localReadPos "+std::to_string(localReadPos)+"; source count "+std::to_string(copyFromCount), E_FILE_LINE); + } + readPos = localReadPos; + size = 0; + } + + // fill with copyFrom elements + if( nullptr != copyFrom && 0 < copyFromCount ) { + if( copyFromCount > capacityPlusOne-1 ) { + throw IllegalArgumentException("copyFrom array length "+std::to_string(copyFromCount)+" > capacity "+toString(), E_FILE_LINE); + } + int localWritePos = writePos; + for(int i=0; i<copyFromCount; i++) { + localWritePos = (localWritePos + 1) % capacityPlusOne; + array[localWritePos] = copyFrom[i]; + size++; + } + writePos = localWritePos; + } + } + + T getImpl(const bool blocking, const bool peek) /* throws InterruptedException */ { + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); // RAII-style acquire and relinquish via destructor + + int localReadPos = readPos; + if( localReadPos == writePos ) { + if( blocking ) { + std::unique_lock<std::mutex> lockRead(syncRead); // RAII-style acquire and relinquish via destructor + while( localReadPos == writePos ) { + cvRead.wait(lockRead); + } + } else { + return nullelem; + } + } + localReadPos = (localReadPos + 1) % capacityPlusOne; + T r = array[localReadPos]; + if( !peek ) { + array[localReadPos] = nullelem; + { + std::unique_lock<std::mutex> lockWrite(syncWrite); // RAII-style acquire and relinquish via destructor + size--; + readPos = localReadPos; + cvWrite.notify_all(); // notify waiting putter + } + } + return r; + } + + bool putImpl(const T &e, const bool sameRef, const bool blocking) /* throws InterruptedException */ { + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); // RAII-style acquire and relinquish via destructor + + int localWritePos = writePos; + localWritePos = (localWritePos + 1) % capacityPlusOne; + if( localWritePos == readPos ) { + if( blocking ) { + std::unique_lock<std::mutex> lockWrite(syncWrite); // RAII-style acquire and relinquish via destructor + while( localWritePos == readPos ) { + cvWrite.wait(lockWrite); + } + } else { + return false; + } + } + if( !sameRef ) { + array[localWritePos] = e; + } + { + std::unique_lock<std::mutex> lockRead(syncRead); // RAII-style acquire and relinquish via destructor + size++; + writePos = localWritePos; + cvRead.notify_all(); // notify waiting getter + } + return true; + } + + public: + std::string toString() const override { + const std::string es = isEmpty() ? ", empty" : ""; + const std::string fs = isFull() ? ", full" : ""; + return "LFRingbuffer<?>[size "+std::to_string(size)+" / "+std::to_string(capacityPlusOne-1)+ + ", writePos "+std::to_string(writePos)+", readPos "+std::to_string(readPos)+es+fs+"]"; + } + + void dump(FILE *stream, std::string prefix) const override { + fprintf(stream, "%s %s {\n", prefix.c_str(), toString().c_str()); + for(int i=0; i<capacityPlusOne; i++) { + // fprintf(stream, "\t[%d]: %p\n", i, array[i].get()); // FIXME + } + fprintf(stream, "}\n"); + } + + /** + * Create a full ring buffer instance w/ the given array's net capacity and content. + * <p> + * Example for a 10 element Integer array: + * <pre> + * Integer[] source = new Integer[10]; + * // fill source with content .. + * Ringbuffer<Integer> rb = new LFRingbuffer<Integer>(source); + * </pre> + * </p> + * <p> + * {@link #isFull()} returns true on the newly created full ring buffer. + * </p> + * <p> + * Implementation will allocate an internal array with size of array <code>copyFrom</code> <i>plus one</i>, + * and copy all elements from array <code>copyFrom</code> into the internal array. + * </p> + * @param copyFrom mandatory source array determining ring buffer's net {@link #capacity()} and initial content. + * @throws IllegalArgumentException if <code>copyFrom</code> is <code>nullptr</code> + */ + LFRingbuffer(const std::vector<T> & copyFrom) /* throws IllegalArgumentException */ + : capacityPlusOne(copyFrom.size() + 1), array(newArray(capacityPlusOne)), + readPos(0), writePos(0), size(0) + { + resetImpl(copyFrom.data(), copyFrom.size()); + } + + LFRingbuffer(const T * copyFrom, const int copyFromSize) /* throws IllegalArgumentException */ + : capacityPlusOne(copyFromSize + 1), array(newArray(capacityPlusOne)), + readPos(0), writePos(0), size(0) + { + resetImpl(copyFrom, copyFromSize); + } + + /** + * Create an empty ring buffer instance w/ the given net <code>capacity</code>. + * <p> + * Example for a 10 element Integer array: + * <pre> + * Ringbuffer<Integer> rb = new LFRingbuffer<Integer>(10, Integer[].class); + * </pre> + * </p> + * <p> + * {@link #isEmpty()} returns true on the newly created empty ring buffer. + * </p> + * <p> + * Implementation will allocate an internal array of size <code>capacity</code> <i>plus one</i>. + * </p> + * @param arrayType the array type of the created empty internal array. + * @param capacity the initial net capacity of the ring buffer + */ + LFRingbuffer(const int capacity) + : capacityPlusOne(capacity + 1), array(newArray(capacityPlusOne)), + readPos(0), writePos(0), size(0) + { } + + ~LFRingbuffer() { + freeArray(array); + } + + LFRingbuffer(const LFRingbuffer &_source) noexcept + : capacityPlusOne(_source.capacityPlusOne), array(newArray(capacityPlusOne)), + readPos(0), writePos(0), size(0) + { + std::unique_lock<std::mutex> lockMultiReadS(_source.syncMultiRead); + std::unique_lock<std::mutex> lockMultiWriteS(_source.syncMultiWrite); + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); + cloneFrom(false, _source); + } + + LFRingbuffer& operator=(const LFRingbuffer &_source) { + std::unique_lock<std::mutex> lockMultiReadS(_source.syncMultiRead); + std::unique_lock<std::mutex> lockMultiWriteS(_source.syncMultiWrite); + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); + + if( this == &_source ) { + return *this; + } + if( capacityPlusOne != _source.capacityPlusOne ) { + cloneFrom(true, _source); + } else { + resetImpl(nullptr, 0 /* empty, nothing to copy */ ); // clear + cloneFrom(false, _source); + } + return *this; + } + + LFRingbuffer(LFRingbuffer &&o) noexcept = default; + LFRingbuffer& operator=(LFRingbuffer &&o) noexcept = default; + + int capacity() const override { return capacityPlusOne-1; } + + void clear() override { + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); // RAII-style acquire and relinquish via destructor + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); // ditto + resetImpl(nullptr, 0 /* empty, nothing to copy */ ); + } + + void reset(const T * copyFrom, const int copyFromCount) override { + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); // RAII-style acquire and relinquish via destructor + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); // ditto + resetImpl(copyFrom, copyFromCount); + } + + void reset(const std::vector<T> & copyFrom) override { + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); // RAII-style acquire and relinquish via destructor + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); // ditto + resetImpl(copyFrom.data(), copyFrom.size()); + } + + int getSize() const override { return size; } + + int getFreeSlots() const override { return capacityPlusOne - 1 - size; } + + bool isEmpty() const override { return writePos == readPos; /* 0 == size */ } + + bool isFull() const override { return ( writePos + 1 ) % capacityPlusOne == readPos ; /* capacityPlusOne - 1 == size */; } + + T get() override { return getImpl(false, false); } + + T getBlocking() override /* throws InterruptedException */ { + return getImpl(true, false); + } + + T peek() override { + return getImpl(false, true); + } + + T peekBlocking() override /* throws InterruptedException */ { + return getImpl(true, true); + } + + bool put(const T & e) override { + return putImpl(e, false, false); + } + + void putBlocking(const T & e) override /* throws InterruptedException */ { + if( !putImpl(e, false, true) ) { + throw InternalError("Blocking put failed: "+toString(), E_FILE_LINE); + } + } + + bool putSame(bool blocking) override /* throws InterruptedException */ { + return putImpl(nullelem, true, blocking); + } + + void waitForFreeSlots(const int count) override /* throws InterruptedException */ { + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); // RAII-style acquire and relinquish via destructor + std::unique_lock<std::mutex> lockRead(syncRead); // RAII-style acquire and relinquish via destructor + + while( capacityPlusOne - 1 - size < count ) { + cvRead.wait(lockRead); + } + } + + void recapacity(const int newCapacity) override { + std::unique_lock<std::mutex> lockMultiRead(syncMultiRead); + std::unique_lock<std::mutex> lockMultiWrite(syncMultiWrite); + + if( capacityPlusOne == newCapacity+1 ) { + return; + } + if( size > newCapacity ) { + throw IllegalArgumentException("amount "+std::to_string(newCapacity)+" < size, "+toString(), E_FILE_LINE); + } + if( 0 > newCapacity ) { + throw IllegalArgumentException("amount "+std::to_string(newCapacity)+" < 0, "+toString(), E_FILE_LINE); + } + + // save current data + int oldCapacityPlusOne = capacityPlusOne; + T * oldArray = array; + int oldReadPos = readPos; + + // new blank resized array + capacityPlusOne = newCapacity + 1; + array = newArray(capacityPlusOne); + readPos = 0; + writePos = 0; + const int _size = size.load(); // fast access + + // copy saved data + if( nullptr != oldArray && 0 < _size ) { + int localWritePos = writePos; + for(int i=0; i<_size; i++) { + localWritePos = (localWritePos + 1) % capacityPlusOne; + oldReadPos = (oldReadPos + 1) % oldCapacityPlusOne; + array[localWritePos] = oldArray[oldReadPos]; + } + writePos = localWritePos; + } + freeArray(oldArray); // and release + } +}; + +} /* namespace direct_bt */ + +#endif /* LFRINGBUFFER_HPP_ */ diff --git a/api/direct_bt/MgmtComm.hpp b/api/direct_bt/MgmtComm.hpp new file mode 100644 index 00000000..e0380151 --- /dev/null +++ b/api/direct_bt/MgmtComm.hpp @@ -0,0 +1,558 @@ +/* + * 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. + */ + +#ifndef MGMT_COMM_HPP_ +#define MGMT_COMM_HPP_ + +#include <cstring> +#include <string> +#include <cstdint> +#include <mutex> + +#include "BTTypes.hpp" +#include "BTIoctl.hpp" +#include "OctetTypes.hpp" +#include "HCIComm.hpp" + +namespace direct_bt { + + class MgmtException : public RuntimeException { + protected: + MgmtException(std::string const type, std::string const m, const char* file, int line) noexcept + : RuntimeException(type, m, file, line) {} + + public: + MgmtException(std::string const m, const char* file, int line) noexcept + : RuntimeException("MgmtException", m, file, line) {} + }; + + class MgmtOpcodeException : public MgmtException { + public: + MgmtOpcodeException(std::string const m, const char* file, int line) noexcept + : MgmtException("MgmtOpcodeException", m, file, line) {} + }; + + enum MgmtConst : uint16_t { + INDEX_NONE = 0xFFFF, + /* Net length, guaranteed to be null-terminated */ + MAX_NAME_LENGTH = 248+1, + MAX_SHORT_NAME_LENGTH = 10+1 + }; + + enum MgmtStatus : uint8_t { + SUCCESS = 0x00, + UNKNOWN_COMMAND = 0x01, + NOT_CONNECTED = 0x02, + FAILED = 0x03, + CONNECT_FAILED = 0x04, + AUTH_FAILED = 0x05, + NOT_PAIRED = 0x06, + NO_RESOURCES = 0x07, + TIMEOUT = 0x08, + ALREADY_CONNECTED = 0x09, + BUSY = 0x0a, + REJECTED = 0x0b, + NOT_SUPPORTED = 0x0c, + INVALID_PARAMS = 0x0d, + DISCONNECTED = 0x0e, + NOT_POWERED = 0x0f, + CANCELLED = 0x10, + INVALID_INDEX = 0x11, + RFKILLED = 0x12, + ALREADY_PAIRED = 0x13, + PERMISSION_DENIED = 0x14 + }; + + std::string mgmt_get_status_string(const MgmtStatus opc); + + enum MgmtOperation : uint16_t { + READ_VERSION = 0x0001, + READ_COMMANDS = 0x0002, + READ_INDEX_LIST = 0x0003, + READ_INFO = 0x0004, + SET_POWERED = 0x0005, + SET_DISCOVERABLE = 0x0006, + SET_CONNECTABLE = 0x0007, + SET_FAST_CONNECTABLE = 0x0008, + SET_BONDABLE = 0x0009, + SET_LINK_SECURITY = 0x000A, + SET_SSP = 0x000B, + SET_HS = 0x000C, + SET_LE = 0x000D, + SET_DEV_CLASS = 0x000E, + SET_LOCAL_NAME = 0x000F, + ADD_UUID = 0x0010, + REMOVE_UUID = 0x0011, + LOAD_LINK_KEYS = 0x0012, + LOAD_LONG_TERM_KEYS = 0x0013, + DISCONNECT = 0x0014, + GET_CONNECTIONS = 0x0015, + PIN_CODE_REPLY = 0x0016, + PIN_CODE_NEG_REPLY = 0x0017, + SET_IO_CAPABILITY = 0x0018, + PAIR_DEVICE = 0x0019, + CANCEL_PAIR_DEVICE = 0x001A, + UNPAIR_DEVICE = 0x001B, + USER_CONFIRM_REPLY = 0x001C, + USER_CONFIRM_NEG_REPLY = 0x001D, + USER_PASSKEY_REPLY = 0x001E, + USER_PASSKEY_NEG_REPLY = 0x001F, + READ_LOCAL_OOB_DATA = 0x0020, + ADD_REMOTE_OOB_DATA = 0x0021, + REMOVE_REMOTE_OOB_DATA = 0x0022, + START_DISCOVERY = 0x0023, + STOP_DISCOVERY = 0x0024, + CONFIRM_NAME = 0x0025, + BLOCK_DEVICE = 0x0026, + UNBLOCK_DEVICE = 0x0027, + SET_DEVICE_ID = 0x0028, + SET_ADVERTISING = 0x0029, + SET_BREDR = 0x002A, + SET_STATIC_ADDRESS = 0x002B, + SET_SCAN_PARAMS = 0x002C, + SET_SECURE_CONN = 0x002D, + SET_DEBUG_KEYS = 0x002E, + SET_PRIVACY = 0x002F, + LOAD_IRKS = 0x0030, + GET_CONN_INFO = 0x0031, + GET_CLOCK_INFO = 0x0032, + ADD_DEVICE = 0x0033, + REMOVE_DEVICE = 0x0034, + LOAD_CONN_PARAM = 0x0035, + READ_UNCONF_INDEX_LIST = 0x0036, + READ_CONFIG_INFO = 0x0037, + SET_EXTERNAL_CONFIG = 0x0038, + SET_PUBLIC_ADDRESS = 0x0039, + START_SERVICE_DISCOVERY = 0x003A, + READ_LOCAL_OOB_EXT_DATA = 0x003B, + READ_EXT_INDEX_LIST = 0x003C, + READ_ADV_FEATURES = 0x003D, + ADD_ADVERTISING = 0x003E, + REMOVE_ADVERTISING = 0x003F, + GET_ADV_SIZE_INFO = 0x0040, + START_LIMITED_DISCOVERY = 0x0041, + READ_EXT_INFO = 0x0042, + SET_APPEARANCE = 0x0043, + GET_PHY_CONFIGURATION = 0x0044, + SET_PHY_CONFIGURATION = 0x0045, + SET_BLOCKED_KEYS = 0x0046 + }; + + std::string mgmt_get_operation_string(const MgmtOperation op); + + enum MgmtOption : uint32_t { + EXTERNAL_CONFIG = 0x00000001, + PUBLIC_ADDRESS = 0x00000002 + }; + + class MgmtRequest + { + public: + enum Opcode : uint16_t { + READ_VERSION = 0x0001, + READ_COMMANDS = 0x0002, + READ_INDEX_LIST = 0x0003, + READ_INFO = 0x0004, + SET_POWERED = 0x0005, // uint8_t bool + SET_DISCOVERABLE = 0x0006, // uint8_t bool [+ uint16_t timeout] + SET_CONNECTABLE = 0x0007, // uint8_t bool + SET_FAST_CONNECTABLE = 0x0008, // uint8_t bool + SET_BONDABLE = 0x0009, // uint8_t bool + SET_LINK_SECURITY = 0x000A, + SET_SSP = 0x000B, + SET_HS = 0x000C, + SET_LE = 0x000D, // uint8_t bool + SET_DEV_CLASS = 0x000E, // uint8_t major, uint8_t minor + SET_LOCAL_NAME = 0x000F // uint8_t name[MAX_NAME_LENGTH], uint8_t short_name[MAX_SHORT_NAME_LENGTH]; + }; + + static std::string getOpcodeString(const Opcode opc); + + protected: + POctets pdu; + + int write(const int dd); + int read(const int dd, uint8_t* buffer, const int capacity, const int timeoutMS); + + static void checkOpcode(const Opcode has, const Opcode min, const Opcode max) + { + if( has < min || has > max ) { + throw MgmtOpcodeException("Has opcode "+uint16HexString(has, true)+ + ", not within range ["+uint16HexString(min, true)+".."+uint16HexString(max, true)+"]", E_FILE_LINE); + } + } + + virtual std::string baseString() const { + return "opcode="+uint8HexString(getOpcode(), true)+" "+getOpcodeString()+", devID "+uint16HexString(getDevID(), true); + } + virtual std::string valueString() const { + const int psz = getParamSize(); + const std::string ps = psz > 0 ? bytesHexString(getParam(), 0, psz, true /* lsbFirst */, true /* leading0X */) : ""; + return "param[size "+std::to_string(getParamSize())+", data "+ps+"], tsz "+std::to_string(getTotalSize()); + } + + public: + + MgmtRequest(const Opcode opc, const uint16_t dev_id, const uint16_t param_size=0) + : pdu(6+param_size) + { + checkOpcode(opc, READ_VERSION, SET_LOCAL_NAME); + + pdu.put_uint16(0, htobs(opc)); + pdu.put_uint16(2, htobs(dev_id)); + pdu.put_uint16(4, htobs(param_size)); + } + MgmtRequest(const Opcode opc, const uint16_t dev_id, const uint16_t param_size, const uint8_t* param) + : MgmtRequest(opc, dev_id, param_size) + { + if( param_size > 0 ) { + memcpy(pdu.get_wptr(6), param, param_size); + } + } + virtual ~MgmtRequest() {} + + int getTotalSize() const { return pdu.getSize(); } + + Opcode getOpcode() const { return static_cast<Opcode>( btohs( pdu.get_uint16(0) ) ); } + std::string getOpcodeString() const { return getOpcodeString(getOpcode()); } + uint16_t getDevID() const { return btohs( pdu.get_uint16(2) ); } + uint16_t getParamSize() const { return btohs( pdu.get_uint16(4) ); } + const uint8_t* getParam() const { return pdu.get_ptr(6); } + + /** writes request to dd and reads result into buffer */ + int send(const int dd, uint8_t* buffer, const int capacity, const int timeoutMS); + + std::string toString() const { + return "MgmtReq["+baseString()+", "+valueString()+"]"; + } + }; + + class MgmtModeReq : public MgmtRequest + { + public: + MgmtModeReq(const Opcode opc, const uint16_t dev_id, const uint8_t mode) + : MgmtRequest(opc, dev_id, 1) + { + checkOpcode(opc, SET_POWERED, SET_LE); + pdu.put_uint8(6, mode); + } + }; + + class MgmtEvent + { + public: + enum Opcode : uint16_t { + CMD_COMPLETE = 0x0001, + CMD_STATUS = 0x0002, + CONTROLLER_ERROR = 0x0003, + INDEX_ADDED = 0x0004, + INDEX_REMOVED = 0x0005, + NEW_SETTINGS = 0x0006, + CLASS_OF_DEV_CHANGED = 0x0007, + LOCAL_NAME_CHANGED = 0x0008, + NEW_LINK_KEY = 0x0009, + NEW_LONG_TERM_KEY = 0x000A, + DEVICE_CONNECTED = 0x000B, + DEVICE_DISCONNECTED = 0x000C, + CONNECT_FAILED = 0x000D, + PIN_CODE_REQUEST = 0x000E, + USER_CONFIRM_REQUEST = 0x000F, + USER_PASSKEY_REQUEST = 0x0010, + AUTH_FAILED = 0x0011, + DEVICE_FOUND = 0x0012, + DISCOVERING = 0x0013, + DEVICE_BLOCKED = 0x0014, + DEVICE_UNBLOCKED = 0x0015, + DEVICE_UNPAIRED = 0x0016, + PASSKEY_NOTIFY = 0x0017, + NEW_IRK = 0x0018, + NEW_CSRK = 0x0019, + DEVICE_ADDED = 0x001A, + DEVICE_REMOVED = 0x001B, + NEW_CONN_PARAM = 0x001C, + UNCONF_INDEX_ADDED = 0x001D, + UNCONF_INDEX_REMOVED = 0x001E, + NEW_CONFIG_OPTIONS = 0x001F, + EXT_INDEX_ADDED = 0x0020, + EXT_INDEX_REMOVED = 0x0021, + LOCAL_OOB_DATA_UPDATED = 0x0022, + ADVERTISING_ADDED = 0x0023, + ADVERTISING_REMOVED = 0x0024, + EXT_INFO_CHANGED = 0x0025, + PHY_CONFIGURATION_CHANGED = 0x0026 + }; + + static std::string getOpcodeString(const Opcode opc); + + protected: + TROOctets pdu; + + static void checkOpcode(const Opcode has, const Opcode min, const Opcode max) + { + if( has < min || has > max ) { + throw MgmtOpcodeException("Has evcode "+uint16HexString(has, true)+ + ", not within range ["+uint16HexString(min, true)+".."+uint16HexString(max, true)+"]", E_FILE_LINE); + } + } + + virtual std::string baseString() const { + return "opcode="+uint8HexString(getOpcode(), true)+" "+getOpcodeString()+", devID "+uint16HexString(getDevID(), true); + } + virtual std::string valueString() const { + const int d_sz = getDataSize(); + const std::string d_str = d_sz > 0 ? bytesHexString(getData(), 0, d_sz, true /* lsbFirst */, true /* leading0X */) : ""; + return "data[size "+std::to_string(d_sz)+", data "+d_str+"], tsz "+std::to_string(getTotalSize()); + } + + public: + + /** + * Return a newly created specialized instance pointer to base class. + * <p> + * Returned memory reference must be deleted by caller. + * </p> + * <p> + * Since we use transient passthrough memory, w/o ownership, + * actual memory is reused and not copied. + * Caller is responsible for the memory lifecycle. + * </p> + */ + static MgmtEvent* getSpecialized(const uint8_t * buffer, int const buffer_size); + + MgmtEvent(const uint8_t* buffer, const int buffer_len) + : pdu(buffer, buffer_len) + { + pdu.check_range(0, 6+getParamSize()); + checkOpcode(getOpcode(), CMD_COMPLETE, PHY_CONFIGURATION_CHANGED); + } + virtual ~MgmtEvent() {} + + int getTotalSize() const { return pdu.getSize(); } + + Opcode getOpcode() const { return static_cast<Opcode>( btohs( pdu.get_uint16(0) ) ); } + std::string getOpcodeString() const { return getOpcodeString(getOpcode()); } + uint16_t getDevID() const { return btohs( pdu.get_uint16(2) ); } + uint16_t getParamSize() const { return btohs( pdu.get_uint16(4) ); } + + virtual const int getDataOffset() const { return 6; } + virtual const int getDataSize() const { return getParamSize(); } + virtual const uint8_t* getData() const { return getDataSize()>0 ? pdu.get_ptr(getDataOffset()) : nullptr; } + + virtual bool validate(const MgmtRequest &req) const { + return req.getDevID() == getDevID(); + } + + std::string toString() const { + return "MgmtEvt["+baseString()+", "+valueString()+"]"; + } + }; + + class MgmtEvtCmdComplete : public MgmtEvent + { + protected: + std::string baseString() const override { + return MgmtEvent::baseString()+", req-opcode="+uint8HexString(getReqOpcode(), true)+" "+MgmtRequest::getOpcodeString(getReqOpcode())+ + ", status "+uint8HexString(getStatus(), true)+" "+mgmt_get_status_string(getStatus()); + } + + public: + static MgmtRequest::Opcode getReqOpcode(const uint8_t *data) { + return static_cast<MgmtRequest::Opcode>( get_uint16(data, 6, true /* littleEndian */) ); + } + + MgmtEvtCmdComplete(const uint8_t* buffer, const int buffer_len) + : MgmtEvent(buffer, buffer_len) + { + checkOpcode(getOpcode(), CMD_COMPLETE, CMD_COMPLETE); + } + MgmtRequest::Opcode getReqOpcode() const { return static_cast<MgmtRequest::Opcode>( btohs( pdu.get_uint16(6) ) ); } + MgmtStatus getStatus() const { return static_cast<MgmtStatus>( pdu.get_uint8(8) ); } + + const int getDataOffset() const override { return 9; } + const int getDataSize() const override { return getParamSize()-3; } + const uint8_t* getData() const override { return getDataSize()>0 ? pdu.get_ptr(getDataOffset()) : nullptr; } + + bool validate(const MgmtRequest &req) const override { + return MgmtEvent::validate(req) && req.getOpcode() == getReqOpcode(); + } + }; + + class MgmtEvtCmdStatus : public MgmtEvent + { + public: + + protected: + std::string baseString() const override { + return MgmtEvent::baseString()+", req-opcode="+uint8HexString(getReqOpcode(), true)+" "+MgmtRequest::getOpcodeString(getReqOpcode())+ + ", status "+uint8HexString(getStatus(), true)+" "+mgmt_get_status_string(getStatus()); + } + + public: + MgmtEvtCmdStatus(const uint8_t* buffer, const int buffer_len) + : MgmtEvent(buffer, buffer_len) + { + checkOpcode(getOpcode(), CMD_STATUS, CMD_STATUS); + } + MgmtRequest::Opcode getReqOpcode() const { return static_cast<MgmtRequest::Opcode>( btohs( pdu.get_uint16(6) ) ); } + MgmtStatus getStatus() const { return static_cast<MgmtStatus>( pdu.get_uint8(8) ); } + + const int getDataOffset() const override { return 9; } + const int getDataSize() const override { return 0; } + const uint8_t* getData() const override { return nullptr; } + + bool validate(const MgmtRequest &req) const override { + return MgmtEvent::validate(req) && req.getOpcode() == getReqOpcode(); + } + }; + + class MgmtEvtAdapterInfo : public MgmtEvtCmdComplete + { + protected: + std::string valueString() const override { + return getMAC().toString()+", version "+std::to_string(getVersion())+ + ", manuf "+std::to_string(getManufacturer())+ + ", settings[sup "+uint32HexString(getSupportedSetting(), true)+", cur "+uint32HexString(getCurrentSetting(), true)+ + "], name "+getName()+", shortName "+getShortName(); + } + + public: + static int getRequiredSize() { return 9 + 20 + MgmtConst::MAX_NAME_LENGTH + MgmtConst::MAX_SHORT_NAME_LENGTH; } + + MgmtEvtAdapterInfo(const uint8_t* buffer, const int buffer_len) + : MgmtEvtCmdComplete(buffer, buffer_len) + { + pdu.check_range(0, getRequiredSize()); + } + + const EUI48 getMAC() const { return EUI48(pdu.get_ptr(getDataOffset()+0)); } + const uint8_t getVersion() const { return pdu.get_uint8(getDataOffset()+6); } + const uint16_t getManufacturer() const { return pdu.get_uint16(getDataOffset()+7); } + const uint32_t getSupportedSetting() const { return pdu.get_uint32(getDataOffset()+9); } + const uint32_t getCurrentSetting() const { return pdu.get_uint32(getDataOffset()+13); } + const uint32_t getDevClass() const { return pdu.get_uint8(getDataOffset()+17) + | ( pdu.get_uint8(getDataOffset()+18) << 8 ) + | ( pdu.get_uint8(getDataOffset()+19) << 16 ); } + const std::string getName() const { return std::string( (const char*)pdu.get_ptr(getDataOffset()+20) ); } + const std::string getShortName() const { return std::string( (const char*)pdu.get_ptr(getDataOffset()+20+MgmtConst::MAX_NAME_LENGTH) ); } + }; + + /** Immutable persistent adapter info */ + class AdapterInfo + { + public: + const int dev_id; + const EUI48 mac; + const uint8_t version; + const uint16_t manufacturer; + const uint32_t supported_setting; + const uint32_t current_setting; + const uint32_t dev_class; + const std::string name; + const std::string short_name; + + AdapterInfo(const MgmtEvtAdapterInfo &s) + : dev_id(s.getDevID()), mac(s.getMAC()), version(s.getVersion()), manufacturer(s.getManufacturer()), + supported_setting(s.getSupportedSetting()), current_setting(s.getCurrentSetting()), + dev_class(s.getDevClass()), name(s.getName()), short_name(s.getShortName()) + { } + + std::string toString() const { + return "Adapter[id "+std::to_string(dev_id)+", mac "+mac.toString()+", version "+std::to_string(version)+ + ", manuf "+std::to_string(manufacturer)+ + ", settings[sup "+uint32HexString(supported_setting, true)+", cur "+uint32HexString(current_setting, true)+ + "], name '"+name+"', shortName '"+short_name+"']"; + } + }; + + // ************************************************* + // ************************************************* + // ************************************************* + + + /** + * A thread safe singleton handler of the Linux Kernel's BlueZ manager control channel. + */ + class MgmtHandler { + private: + std::recursive_mutex mtx; + const int ibuffer_size = 512; + uint8_t ibuffer[512]; + std::vector<std::shared_ptr<const AdapterInfo>> adapters; + HCIComm comm; + + int read(uint8_t* buffer, const int capacity, const int timeoutMS); + int write(const uint8_t * buffer, const int length); + + MgmtHandler(); + MgmtHandler(const MgmtHandler&) = delete; + void operator=(const MgmtHandler&) = delete; + void close(); + + bool initAdapter(const uint16_t dev_id); + + public: + /** + * Retrieves the singleton instance. + * <p> + * First call will open and initialize the bluetooth kernel. + * </p> + */ + static MgmtHandler& get() { + /** + * Thread safe starting with C++11 6.7: + * + * If control enters the declaration concurrently while the variable is being initialized, + * the concurrent execution shall wait for completion of the initialization. + * + * (Magic Statics) + * + * Avoiding non-working double checked locking. + */ + static MgmtHandler s; + return s; + } + ~MgmtHandler() { close(); } + + /** Returns true if this mgmt instance is open and hence valid, otherwise false */ + bool isOpen() const { + return comm.isOpen(); + } + bool setMode(const int dev_id, const MgmtModeReq::Opcode opc, const uint8_t mode); + + /** + * In case response size check or devID and optional opcode validation fails, + * function returns NULL. + */ + std::shared_ptr<MgmtEvent> send(MgmtRequest &req, uint8_t* buffer, const int capacity, const int timeoutMS); + + const std::vector<std::shared_ptr<const AdapterInfo>> getAdapters() const { return adapters; } + int getDefaultAdapterIdx() const { return adapters.size() > 0 ? 0 : -1; } + int findAdapterIdx(const EUI48 &mac) const; + std::shared_ptr<const AdapterInfo> getAdapter(const int idx) const; + }; + +} // namespace direct_bt + +#endif /* MGMT_COMM_HPP_ */ diff --git a/api/direct_bt/OctetTypes.hpp b/api/direct_bt/OctetTypes.hpp new file mode 100644 index 00000000..48afb27a --- /dev/null +++ b/api/direct_bt/OctetTypes.hpp @@ -0,0 +1,352 @@ +/* + * 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. + */ + +#ifndef OCTET_TYPES_HPP_ +#define OCTET_TYPES_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include <mutex> +#include <atomic> + +namespace direct_bt { + + /** + * Transient read only octet data, i.e. non persistent passthrough, owned by caller. + * <p> + * Either ATT value (Vol 3, Part F 3.2.4) or PDU data. + * </p> + */ + class TROOctets + { + private: + int _size; + uint8_t * _data; + + protected: + inline uint8_t * data() { return _data; } + inline void setData(uint8_t *d, int s) { _data = d; _size = s; } + inline void setSize(int s) { _size = s; } + + public: + /** Transient passthrough read-only memory, w/o ownership ..*/ + TROOctets(const uint8_t *source, const int len) + : _size( len ), _data( const_cast<uint8_t *>(source) ) { } + + TROOctets(const TROOctets &o) noexcept = default; + TROOctets(TROOctets &&o) noexcept = default; + TROOctets& operator=(const TROOctets &o) noexcept = default; + TROOctets& operator=(TROOctets &&o) noexcept = default; + + inline void check_range(const int i, const int count) const { + if( 0 > i || i+count > _size ) { + throw IndexOutOfBoundsException(i, count, _size, E_FILE_LINE); + } + } + int getSize() const { return _size; } + + uint8_t get_uint8(const int i) const { + check_range(i, 1); + return _data[i]; + } + + uint16_t get_uint16(const int i) const { + check_range(i, 2); + return direct_bt::get_uint16(_data, i, true /* littleEndian */); + } + + uint32_t get_uint32(const int i) const { + check_range(i, 4); + return direct_bt::get_uint32(_data, i, true /* littleEndian */); + } + + uuid16_t get_uuid16(const int i) const { + return uuid16_t(get_uint16(i)); + } + uuid128_t get_uuid128(const int i) const { + check_range(i, uuid_t::TypeSize::UUID128_SZ); + return uuid128_t(get_uint128(_data, i, true /* littleEndian */)); + } + std::shared_ptr<const uuid_t> get_uuid(const int i, const uuid_t::TypeSize tsize) const { + check_range(i, tsize); + return uuid_t::create(tsize, _data, i, true /* littleEndian */); + } + + uint8_t const * get_ptr() const { return _data; } + uint8_t const * get_ptr(const int i) const { + check_range(i, 1); + return _data + i; + } + + std::string toString() const { + return "size "+std::to_string(_size)+", ro: "+bytesHexString(_data, 0, _size, true /* lsbFirst */, true /* leading0X */); + } + }; + + /** + * Transient octet data, i.e. non persistent passthrough, owned by caller. + * <p> + * Either ATT value (Vol 3, Part F 3.2.4) or PDU data. + * </p> + */ + class TOctets : public TROOctets + { + public: + /** Transient passthrough r/w memory, w/o ownership ..*/ + TOctets(uint8_t *source, const int len) + : TROOctets(source, len) {} + + TOctets(const TOctets &o) noexcept = default; + TOctets(TOctets &&o) noexcept = default; + TOctets& operator=(const TOctets &o) noexcept = default; + TOctets& operator=(TOctets &&o) noexcept = default; + + void put_uint8(const int i, const uint8_t v) { + check_range(i, 1); + data()[i] = v;; + } + + void put_uint16(const int i, const uint16_t v) { + check_range(i, 2); + direct_bt::put_uint16(data(), i, v, true /* littleEndian */); + } + + void put_uint32(const int i, const uint32_t v) { + check_range(i, 4); + direct_bt::put_uint32(data(), i, v, true /* littleEndian */); + } + + void put_uuid(const int i, const uuid_t & v) { + check_range(i, v.getTypeSize()); + direct_bt::put_uuid(data(), i, v, true /* littleEndian */); + } + + uint8_t * get_wptr() { return data(); } + uint8_t * get_wptr(const int i) { + check_range(i, 1); + return data() + i; + } + + std::string toString() const { + return "size "+std::to_string(getSize())+", rw: "+bytesHexString(get_ptr(), 0, getSize(), true /* lsbFirst */, true /* leading0X */); + } + }; + + class TOctetSlice + { + private: + const TOctets & parent; + int const offset; + int const size; + + public: + TOctetSlice(const TOctets &buffer, const int offset, const int len) + : parent(buffer), offset(offset), size(len) + { + if( offset+size > buffer.getSize() ) { + throw IndexOutOfBoundsException(offset, size, buffer.getSize(), E_FILE_LINE); + } + } + + int getSize() const { return size; } + int getOffset() const { return offset; } + const TOctets& getParent() const { return parent; } + + uint8_t get_uint8(const int i) const { + return parent.get_uint8(offset+i); + } + + uint16_t get_uint16(const int i) const { + return parent.get_uint16(offset+i); + } + + uint8_t const * get_ptr(const int i) const { + return parent.get_ptr(offset+i); + } + + std::string toString() const { + return "offset "+std::to_string(offset)+", size "+std::to_string(size)+": "+bytesHexString(parent.get_ptr(), offset, size, true /* lsbFirst */, true /* leading0X */); + } + }; + + /** + * Persistent octet data, i.e. owned memory allocation. + * <p> + * GATT value (Vol 3, Part F 3.2.4) + * </p> + */ + class POctets : public TOctets + { + private: + int capacity; + + public: + /** Takes ownership (malloc and copy, free) ..*/ + POctets(const uint8_t *_source, const int _size) + : TOctets( static_cast<uint8_t*>( std::malloc(_size) ), _size), + capacity( _size ) + { + std::memcpy(get_wptr(), _source, _size); + } + + /** Blank zeroed buffer (calloc, free) */ + POctets(const int _capacity, const int _size) + : TOctets( static_cast<uint8_t*>( std::malloc(_capacity) ), _size), + capacity( _capacity ) + { + if( capacity < getSize() ) { + throw IllegalArgumentException("capacity "+std::to_string(capacity)+" < size "+std::to_string(getSize()), E_FILE_LINE); + } + } + + /** Blank zeroed buffer (calloc, free) */ + POctets(const int size) + : POctets(size, size) + { } + + /** Makes a transient TOctets persistent by taking ownership (malloc and copy, free) ..*/ + POctets(const TOctets & _source) + : TOctets( static_cast<uint8_t*>( std::malloc(_source.getSize()) ), _source.getSize()), + capacity( _source.getSize() ) + { + std::memcpy(get_wptr(), _source.get_ptr(), _source.getSize()); + } + /** Makes a transient TOctetSlice persistent by taking ownership (malloc and copy, free) ..*/ + POctets(const TOctetSlice & _source) + : TOctets( static_cast<uint8_t*>( std::malloc(_source.getSize()) ), _source.getSize()), + capacity( _source.getSize() ) + { + std::memcpy(get_wptr(), _source.getParent().get_ptr() + _source.getOffset(), _source.getSize()); + } + ~POctets() { release(); } + + POctets(const POctets &_source) noexcept + : TOctets( static_cast<uint8_t*>( std::malloc(_source.getSize()) ), _source.getSize()), + capacity( _source.getCapacity() ) + { + std::memcpy(get_wptr(), _source.get_ptr(), _source.getSize()); + } + + POctets& operator=(const POctets &_source) { + if( this == &_source ) { + return *this; + } + release(); + setData(static_cast<uint8_t*>( std::malloc(_source.getSize()) ), _source.getSize()); + capacity = _source.getSize(); + return *this; + } + + POctets(POctets &&o) noexcept = default; + POctets& operator=(POctets &&o) noexcept = default; + + void release() { + free(get_wptr()); + setData(nullptr, 0); + capacity=0; + } + + POctets & resize(const int newSize, const int newCapacity) { + if( newCapacity < newSize ) { + throw IllegalArgumentException("newCapacity "+std::to_string(newCapacity)+" < newSize "+std::to_string(newSize), E_FILE_LINE); + } + if( newCapacity != capacity ) { + if( newSize > getSize() ) { + recapacity(newCapacity); + setSize(newSize); + } else { + setSize(newSize); + recapacity(newCapacity); + } + } else { + setSize(newSize); + } + return *this; + } + + POctets & resize(const int newSize) { + if( capacity < newSize ) { + throw IllegalArgumentException("capacity "+std::to_string(capacity)+" < newSize "+std::to_string(newSize), E_FILE_LINE); + } + setSize(newSize); + return *this; + } + + POctets & recapacity(const int newCapacity) { + if( newCapacity < getSize() ) { + throw IllegalArgumentException("newCapacity "+std::to_string(newCapacity)+" < size "+std::to_string(getSize()), E_FILE_LINE); + } + if( newCapacity == capacity ) { + return *this; + } + uint8_t* data2 = static_cast<uint8_t*>( std::malloc(newCapacity) ); + if( getSize() > 0 ) { + memcpy(data2, get_ptr(), getSize()); + } + free(get_wptr()); + setData(data2, getSize()); + capacity = newCapacity; + return *this; + } + + int getCapacity() const { return capacity; } + + POctets & operator+=(const TOctets &b) { + if( 0 < b.getSize() ) { + const int newSize = getSize() + b.getSize(); + if( capacity < newSize ) { + recapacity( newSize ); + } + memcpy(get_wptr()+getSize(), b.get_ptr(), b.getSize()); + setSize(newSize); + } + return *this; + } + POctets & operator+=(const TOctetSlice &b) { + if( 0 < b.getSize() ) { + const int newSize = getSize() + b.getSize(); + if( capacity < newSize ) { + recapacity( newSize ); + } + memcpy(get_wptr()+getSize(), b.getParent().get_ptr()+b.getOffset(), b.getSize()); + setSize(newSize); + } + return *this; + } + + std::string toString() const { + return "size "+std::to_string(getSize())+", capacity "+std::to_string(getCapacity())+": "+bytesHexString(get_ptr(), 0, getSize(), true /* lsbFirst */, true /* leading0X */); + } + }; + + +} + + +#endif /* OCTET_TYPES_HPP_ */ diff --git a/api/direct_bt/Ringbuffer.hpp b/api/direct_bt/Ringbuffer.hpp new file mode 100644 index 00000000..74468693 --- /dev/null +++ b/api/direct_bt/Ringbuffer.hpp @@ -0,0 +1,182 @@ +/* + * 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. + */ + +#ifndef RINGBUFFER_HPP_ +#define RINGBUFFER_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> + +#include "BasicTypes.hpp" + +namespace direct_bt { + + +/** + * Ring buffer interface, a.k.a circular buffer. + * <p> + * Caller can chose whether to block until get / put is able to proceed or not. + * </p> + * <p> + * Caller can chose whether to pass an empty array and clear references at get, + * or using a preset array for circular access of same objects. + * </p> + * <p> + * Synchronization and hence thread safety details belong to the implementation. + * </p> + */ +template <class T> class Ringbuffer { + public: + virtual ~Ringbuffer() {} + + /** Returns a short string representation incl. size/capacity and internal r/w index (impl. dependent). */ + virtual std::string toString() const = 0; + + /** Debug functionality - Dumps the contents of the internal array. */ + virtual void dump(FILE *stream, std::string prefix) const = 0; + + /** Returns the net capacity of this ring buffer. */ + virtual int capacity() const = 0; + + /** + * Releasing all elements by assigning <code>null</code>. + * <p> + * {@link #isEmpty()} will return <code>true</code> and + * {@link #getSize()} will return <code>0</code> after calling this method. + * </p> + */ + virtual void clear() = 0; + + /** + * {@link #clear()} all elements and add all <code>copyFrom</code> elements thereafter. + * @param copyFrom Mandatory array w/ length {@link #capacity()} to be copied into the internal array. + */ + virtual void reset(const T * copyFrom, const int copyFromCount) = 0; + virtual void reset(const std::vector<T> & copyFrom) = 0; + + /** Returns the number of elements in this ring buffer. */ + virtual int getSize() const = 0; + + /** Returns the number of free slots available to put. */ + virtual int getFreeSlots() const = 0; + + /** Returns true if this ring buffer is empty, otherwise false. */ + virtual bool isEmpty() const = 0; + + /** Returns true if this ring buffer is full, otherwise false. */ + virtual bool isFull() const = 0; + + /** + * Dequeues the oldest enqueued element if available, otherwise null. + * <p> + * The returned ring buffer slot will be set to <code>null</code> to release the reference + * and move ownership to the caller. + * </p> + * <p> + * Method is non blocking and returns immediately;. + * </p> + * @return the oldest put element if available, otherwise null. + */ + virtual T get() = 0; + + /** + * Dequeues the oldest enqueued element. + * <p> + * The returned ring buffer slot will be set to <code>null</code> to release the reference + * and move ownership to the caller. + * </p> + * <p> + * Methods blocks until an element becomes available via put. + * </p> + * @return the oldest put element + * @throws InterruptedException + */ + virtual T getBlocking() /* throws InterruptedException */ = 0; + + /** + * Peeks the next element at the read position w/o modifying pointer, nor blocking. + * @return <code>null</code> if empty, otherwise the element which would be read next. + */ + virtual T peek() = 0; + + /** + * Peeks the next element at the read position w/o modifying pointer, but w/ blocking. + * @return <code>null</code> if empty, otherwise the element which would be read next. + */ + virtual T peekBlocking() /* throws InterruptedException */ = 0; + + /** + * Enqueues the given element. + * <p> + * Returns true if successful, otherwise false in case buffer is full. + * </p> + * <p> + * Method is non blocking and returns immediately;. + * </p> + */ + virtual bool put(const T & e) = 0; + + /** + * Enqueues the given element. + * <p> + * Method blocks until a free slot becomes available via get. + * </p> + * @throws InterruptedException + */ + virtual void putBlocking(const T & e) /* throws InterruptedException */ = 0; + + /** + * Enqueues the same element at it's write position, if not full. + * <p> + * Returns true if successful, otherwise false in case buffer is full. + * </p> + * <p> + * If <code>blocking</code> is true, method blocks until a free slot becomes available via get. + * </p> + * @param blocking if true, wait until a free slot becomes available via get. + * @throws InterruptedException + */ + virtual bool putSame(const bool blocking) /* throws InterruptedException */ = 0; + + /** + * Blocks until at least <code>count</code> free slots become available. + * @throws InterruptedException + */ + virtual void waitForFreeSlots(const int count) /* throws InterruptedException */ = 0; + + /** + * Resizes this ring buffer's capacity. + * <p> + * New capacity must be greater than current size. + * </p> + */ + virtual void recapacity(const int newCapacity) = 0; +}; + +} /* namespace direct_bt */ + +#endif /* RINGBUFFER_HPP_ */ diff --git a/api/direct_bt/UUID.hpp b/api/direct_bt/UUID.hpp new file mode 100644 index 00000000..b79ed3e2 --- /dev/null +++ b/api/direct_bt/UUID.hpp @@ -0,0 +1,242 @@ +/* + * 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. + */ + +#ifndef UUID_HPP_ +#define UUID_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> + +#include "BasicTypes.hpp" + +namespace direct_bt { + +class uuid128_t; // forward + +/** + * Bluetooth UUID <https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/> + * <p> + * Bluetooth is LSB or Little-Endian! + * </p> + * <p> + * BASE_UUID '00000000-0000-1000-8000-00805F9B34FB' + * </p> + */ +extern uuid128_t BT_BASE_UUID; + +class uuid_t { +public: + /** Underlying integer value present octet count */ + enum TypeSize : int { + UUID16_SZ=2, UUID32_SZ=4, UUID128_SZ=16 + }; + +private: + TypeSize type; + +protected: + uuid_t(TypeSize const type) : type(type) {} + +public: + static TypeSize toTypeSize(const int size); + static std::shared_ptr<const uuid_t> create(TypeSize const t, uint8_t const * const buffer, int const byte_offset, bool const littleEndian); + + virtual ~uuid_t() {} + + uuid_t(const uuid_t &o) noexcept = default; + uuid_t(uuid_t &&o) noexcept = default; + uuid_t& operator=(const uuid_t &o) noexcept = default; + uuid_t& operator=(uuid_t &&o) noexcept = default; + + virtual bool operator==(uuid_t const &o) const { + if( this == &o ) { + return true; + } + return type == o.type; + } + bool operator!=(uuid_t const &o) const + { return !(*this == o); } + + TypeSize getTypeSize() const { return type; } + uuid128_t toUUID128(uuid128_t const & base_uuid=BT_BASE_UUID, int const uuid32_le_octet_index=12) const; + /** returns the pointer to the uuid data of size getTypeSize() */ + virtual const uint8_t * data() const { return nullptr; } + virtual std::string toString() const { return ""; } + virtual std::string toUUID128String(uuid128_t const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const; +}; + +class uuid16_t : public uuid_t { +public: + uint16_t value; + + uuid16_t(uint16_t const v) + : uuid_t(TypeSize::UUID16_SZ), value(v) { } + + uuid16_t(uint8_t const * const buffer, int const byte_offset, bool const littleEndian) + : uuid_t(TypeSize::UUID16_SZ), value(get_uint16(buffer, byte_offset, littleEndian)) { } + + uuid16_t(const uuid16_t &o) noexcept = default; + uuid16_t(uuid16_t &&o) noexcept = default; + uuid16_t& operator=(const uuid16_t &o) noexcept = default; + uuid16_t& operator=(uuid16_t &&o) noexcept = default; + + bool operator==(uuid_t const &o) const override { + if( this == &o ) { + return true; + } + return getTypeSize() == o.getTypeSize() && value == static_cast<uuid16_t const *>(&o)->value; + } + + const uint8_t * data() const override { return static_cast<uint8_t*>(static_cast<void*>(const_cast<uint16_t*>(&value))); } + std::string toString() const override; + std::string toUUID128String(uuid128_t const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override; +}; + +class uuid32_t : public uuid_t { +public: + uint32_t value; + + uuid32_t(uint32_t const v) + : uuid_t(TypeSize::UUID32_SZ), value(v) {} + + uuid32_t(uint8_t const * const buffer, int const byte_offset, bool const littleEndian) + : uuid_t(TypeSize::UUID32_SZ), value(get_uint32(buffer, byte_offset, littleEndian)) { } + + uuid32_t(const uuid32_t &o) noexcept = default; + uuid32_t(uuid32_t &&o) noexcept = default; + uuid32_t& operator=(const uuid32_t &o) noexcept = default; + uuid32_t& operator=(uuid32_t &&o) noexcept = default; + + bool operator==(uuid_t const &o) const override { + if( this == &o ) { + return true; + } + return getTypeSize() == o.getTypeSize() && value == static_cast<uuid32_t const *>(&o)->value; + } + + const uint8_t * data() const override { return static_cast<uint8_t*>(static_cast<void*>(const_cast<uint32_t*>(&value))); } + std::string toString() const override; + std::string toUUID128String(uuid128_t const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override; +}; + +class uuid128_t : public uuid_t { +public: + uint128_t value; + + uuid128_t() : uuid_t(TypeSize::UUID128_SZ) { bzero(value.data, sizeof(value)); } + + uuid128_t(uint128_t const v) + : uuid_t(TypeSize::UUID128_SZ), value(v) {} + + uuid128_t(const std::string str); + + uuid128_t(uint8_t const * const buffer, int const byte_offset, bool const littleEndian) + : uuid_t(TypeSize::UUID128_SZ), value(get_uint128(buffer, byte_offset, littleEndian)) { } + + uuid128_t(uuid16_t const & uuid16, uuid128_t const & base_uuid=BT_BASE_UUID, int const uuid16_le_octet_index=12); + + uuid128_t(uuid32_t const & uuid32, uuid128_t const & base_uuid=BT_BASE_UUID, int const uuid32_le_octet_index=12); + + uuid128_t(const uuid128_t &o) noexcept = default; + uuid128_t(uuid128_t &&o) noexcept = default; + uuid128_t& operator=(const uuid128_t &o) noexcept = default; + uuid128_t& operator=(uuid128_t &&o) noexcept = default; + + bool operator==(uuid_t const &o) const override { + if( this == &o ) { + return true; + } + return getTypeSize() == o.getTypeSize() && value == static_cast<uuid128_t const *>(&o)->value; + } + + const uint8_t * data() const override { return value.data; } + std::string toString() const override; + std::string toUUID128String(uuid128_t const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override { + (void)base_uuid; + (void)le_octet_index; + return toString(); + } +}; + +inline void put_uuid(uint8_t * buffer, int const byte_offset, const uuid_t &v) +{ + switch(v.getTypeSize()) { + case uuid_t::TypeSize::UUID16_SZ: + put_uint16(buffer, byte_offset, static_cast<const uuid16_t &>(v).value); + break; + case uuid_t::TypeSize::UUID32_SZ: + put_uint32(buffer, byte_offset, static_cast<const uuid32_t &>(v).value); + break; + case uuid_t::TypeSize::UUID128_SZ: + put_uint128(buffer, byte_offset, static_cast<const uuid128_t &>(v).value); + break; + } +} +inline void put_uuid(uint8_t * buffer, int const byte_offset, const uuid_t &v, bool littleEndian) +{ + switch(v.getTypeSize()) { + case uuid_t::TypeSize::UUID16_SZ: + put_uint16(buffer, byte_offset, static_cast<const uuid16_t &>(v).value, littleEndian); + break; + case uuid_t::TypeSize::UUID32_SZ: + put_uint32(buffer, byte_offset, static_cast<const uuid32_t &>(v).value, littleEndian); + break; + case uuid_t::TypeSize::UUID128_SZ: + put_uint128(buffer, byte_offset, static_cast<const uuid128_t &>(v).value, littleEndian); + break; + } +} + +inline uuid16_t get_uuid16(uint8_t const * buffer, int const byte_offset) +{ + return uuid16_t(get_uint16(buffer, byte_offset)); +} +inline uuid16_t get_uuid16(uint8_t const * buffer, int const byte_offset, bool littleEndian) +{ + return uuid16_t(get_uint16(buffer, byte_offset, littleEndian)); +} +inline uuid32_t get_uuid32(uint8_t const * buffer, int const byte_offset) +{ + return uuid32_t(get_uint32(buffer, byte_offset)); +} +inline uuid32_t get_uuid32(uint8_t const * buffer, int const byte_offset, bool littleEndian) +{ + return uuid32_t(get_uint32(buffer, byte_offset, littleEndian)); +} +inline uuid128_t get_uuid128(uint8_t const * buffer, int const byte_offset) +{ + return uuid128_t(get_uint128(buffer, byte_offset)); +} +inline uuid128_t get_uuid128(uint8_t const * buffer, int const byte_offset, bool littleEndian) +{ + return uuid128_t(get_uint128(buffer, byte_offset, littleEndian)); +} + +} /* namespace direct_bt */ + +#endif /* UUID_HPP_ */ diff --git a/api/direct_bt/linux_kernel_types.hpp b/api/direct_bt/linux_kernel_types.hpp new file mode 100644 index 00000000..dca8b227 --- /dev/null +++ b/api/direct_bt/linux_kernel_types.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2020 ZAFENA AB + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef LINUX_KERNEL_TYPES_HPP_ +#define LINUX_KERNEL_TYPES_HPP_ + +typedef uint8_t __u8; +typedef int8_t __s8; +typedef uint16_t __u16; +typedef uint16_t __le16; +typedef uint16_t __be16; +typedef uint32_t __u32; +typedef uint32_t __le32; +typedef uint32_t __be32; +typedef uint64_t __u64; +typedef uint64_t __le64; +typedef uint64_t __be64; +typedef EUI48 bdaddr_t; +#define __packed __attribute__ ((packed)) + +#endif /* LINUX_KERNEL_TYPES_HPP_ */ diff --git a/api/ieee11073/DataTypes.hpp b/api/ieee11073/DataTypes.hpp new file mode 100644 index 00000000..b49295db --- /dev/null +++ b/api/ieee11073/DataTypes.hpp @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#ifndef IEEE11073_TYPES_HPP_ +#define IEEE11073_TYPES_HPP_ + +#include <cstring> +#include <string> +#include <memory> +#include <cstdint> +#include <vector> +#include <cmath> + +extern "C" { + #include <endian.h> + #include <byteswap.h> +} + +/** + * IEEE11073 Data Types + * <p> + * https://en.wikipedia.org/wiki/ISO/IEEE_11073_Personal_Health_Data_(PHD)_Standards + * https://person.hst.aau.dk/ska/MIE2008/ParalleSessions/PresentationsForDownloads/Mon-1530/Sta-30_Clarke.pdf + * </p> + * <p> + * https://en.wikipedia.org/wiki/ISO/IEEE_11073 + * http://www.11073.org/ + * </p> + */ +namespace ieee11073 { + + #define E_FILE_LINE __FILE__,__LINE__ + + class RuntimeException : public std::exception { + protected: + RuntimeException(std::string const type, std::string const m, const char* file, int line) noexcept + : msg(std::string(type).append(" @ ").append(file).append(":").append(std::to_string(line)).append(": ").append(m)) { } + + public: + const std::string msg; + + RuntimeException(std::string const m, const char* file, int line) noexcept + : RuntimeException("RuntimeException", m, file, line) {} + + virtual ~RuntimeException() noexcept { } + + virtual const char* what() const noexcept override; + }; + + /** date / timestamp format */ + class AbsoluteTime { + public: + int16_t year=0; + int8_t month=0; + int8_t day=0; + int8_t hour=0; + int8_t minute=0; + int8_t second=0; + int8_t second_fractions=0; + + /** Default ctor w/ zero value */ + AbsoluteTime() {} + + /** Reads up to 8 bytes, as available */ + AbsoluteTime(const uint8_t * data_le, const int size); + + AbsoluteTime(const AbsoluteTime &o) noexcept = default; + AbsoluteTime(AbsoluteTime &&o) noexcept = default; + AbsoluteTime& operator=(const AbsoluteTime &o) noexcept = default; + AbsoluteTime& operator=(AbsoluteTime &&o) noexcept = default; + + std::string toString() const; + }; + + /** + * IEEE11073 Float Data Types + */ + class FloatTypes { + public: + enum ReservedFloatValues : int32_t { + MDER_POSITIVE_INFINITY = 0x007FFFFE, + MDER_NaN = 0x007FFFFF, + MDER_NRes = 0x00800000, + MDER_RESERVED_VALUE = 0x00800001, + MDER_NEGATIVE_INFINITY = 0x00800002 + }; + + enum ReservedSFloatValues : int16_t { + MDER_S_POSITIVE_INFINITY = 0x07FE, + MDER_S_NaN = 0x07FF, + MDER_S_NRes = 0x0800, + MDER_S_RESERVED_VALUE = 0x0801, + MDER_S_NEGATIVE_INFINITY = 0x0802 + }; + + /** + * Converts a 'IEEE-11073 16-bit SFLOAT' to std IEEE754 float. + * <p> + * raw_bt_float16_le is in little-endian, 2 bytes. Exponent at highest byte. + * </p> + */ + static float float16_IEEE11073_to_IEEE754(const uint16_t raw_bt_float16_le); + + /** + * Converts a 'IEEE-11073 32-bit FLOAT' to std IEEE754 float. + * <p> + * Example: Temperature measurement, GattCharacteristicType::TEMPERATURE_MEASUREMENT. + * </p> + * <p> + * raw_bt_float32_le is in little-endian, 4 bytes. Exponent at highest byte. + * </p> + */ + static float float32_IEEE11073_to_IEEE754(const uint32_t raw_bt_float32_le); + }; + +} // namespace direct_bt + +#endif /* IEEE11073_TYPES_HPP_ */ diff --git a/api/tinyb_hci/BTDataTypes.hpp b/api/tinyb_hci/BTDataTypes.hpp deleted file mode 100644 index f47c6262..00000000 --- a/api/tinyb_hci/BTDataTypes.hpp +++ /dev/null @@ -1,373 +0,0 @@ -/* - * 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. - */ - -#ifndef BTDATATYPES_HPP_ -#define BTDATATYPES_HPP_ - -#pragma once -#include <cstring> -#include <string> -#include <memory> -#include <cstdint> -#include <vector> - -#include "BasicTypes.hpp" -#include "UUID.hpp" - -namespace tinyb_hci { - -enum AD_Type_Const : uint8_t { - AD_FLAGS_LIMITED_MODE_BIT = 0x01, - AD_FLAGS_GENERAL_MODE_BIT = 0x02 -}; - -/** - * ​​Assigned numbers are used in Generic Access Profile (GAP) for inquiry response, - * EIR data type values, manufacturer-specific data, advertising data, - * low energy UUIDs and appearance characteristics, and class of device. - * <p> - * Type identifier values as defined in "Assigned Numbers - Generic Access Profile" - * <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> - * </p> - * <p> - * Also see Bluetooth Core Specification Supplement V9, Part A: 1, p 9 pp - * for data format definitions. - * </p> - * <p> - * For data segment layout see Bluetooth Core Specification V5.2 [Vol. 3, Part C, 11, p 1392] - * </p> - * <p> - * https://www.bluetooth.com/specifications/archived-specifications/ - * </p> - */ -enum GAP_T : uint8_t { - // Last sync 2020-02-17 with <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> - /** Flags */ - FLAGS = 0x01, - /** Incomplete List of 16-bit Service Class UUID. (Supplement, Part A, section 1.1)*/ - UUID16_INCOMPLETE = 0x02, - /** Complete List of 16-bit Service Class UUID. (Supplement, Part A, section 1.1) */ - UUID16_COMPLETE = 0x03, - /** Incomplete List of 32-bit Service Class UUID. (Supplement, Part A, section 1.1) */ - UUID32_INCOMPLETE = 0x04, - /** Complete List of 32-bit Service Class UUID. (Supplement, Part A, section 1.1) */ - UUID32_COMPLETE = 0x05, - /** Incomplete List of 128-bit Service Class UUID. (Supplement, Part A, section 1.1) */ - UUID128_INCOMPLETE = 0x06, - /** Complete List of 128-bit Service Class UUID. (Supplement, Part A, section 1.1) */ - UUID128_COMPLETE = 0x07, - /** Shortened local name (Supplement, Part A, section 1.2) */ - NAME_LOCAL_SHORT = 0x08, - /** Complete local name (Supplement, Part A, section 1.2) */ - NAME_LOCAL_COMPLETE = 0x09, - /** Transmit power level (Supplement, Part A, section 1.5) */ - TX_POWER_LEVEL = 0x0A, - - /** - * SSP: Secure Simple Pairing Out of Band: Supplement, Part A, section 1.6 - * Supplement, Part A, Section 1.6: SSP OOB Data Block w/ SSP_OOB_LEN ([Vol 3] Part C, Section 5.2.2.7.) - * <p> - * SSP Class of device (Supplement, Part A, section 1.6). - * </p> - */ - SSP_CLASS_OF_DEVICE = 0x0D, - /** SSP: Simple Pairing Hash C and Simple Pairing Hash C-192 (Supplement, Part A 1.6) */ - SSP_HASH_C192 = 0x0E, - /** SSP: Simple Pairing Randomizer R-192 (Supplement, Part A, section 1.6) */ - SSP_RANDOMIZER_R192 = 0x0F, - - /** Device ID Profile v 1.3 or later */ - DEVICE_ID = 0x10, - - /** Security Manager TK Value (Supplement, Part A, section 1.8) */ - SEC_MGR_TK_VALUE = 0x10, - - /** Security Manager Out of Band Flags (Supplement, Part A, section 1.7) */ - SEC_MGR_OOB_FLAGS = 0x11, - - /** Slave Connection Interval Range */ - SLAVE_CONN_IVAL_RANGE = 0x12, - - /** List of 16-bit Service Solicitation UUIDs (Supplement, Part A, section 1.10) */ - SOLICIT_UUID16 = 0x14, - - /** List of 128-bit Service Solicitation UUIDs (Supplement, Part A, section 1.10) */ - SOLICIT_UUID128 = 0x15, - - /** Service Data - 16-bit UUID (Supplement, Part A, section 1.11) */ - SVC_DATA_UUID16 = 0x16, - - /* Public Target Address (Supplement, Part A, section 1.13) */ - PUB_TRGT_ADDR = 0x17, - /* Random Target Address (Supplement, Part A, section 1.14) */ - RND_TRGT_ADDR = 0x18, - - /** (GAP) Appearance (Supplement, Part A, section 1.12) */ - GAP_APPEARANCE = 0x19, - - /** Advertising Interval (Supplement, Part A, section 1.15) */ - ADV_INTERVAL = 0x1A, - /** LE Bluetooth Device Address */ - LE_BT_DEV_ADDRESS = 0x1B, - /** LE ROLE */ - LE_ROLE = 0x1C, - - /** SSP: Simple Pairing Hash C-256 (Supplement, Part A 1.6) */ - SSP_HASH_C256 = 0x1D, - /** SSP: Simple Pairing Randomizer R-256 (Supplement, Part A, section 1.6) */ - SSP_RANDOMIZER_R256 = 0x1E, - - /** List of 32-bit Service Solicitation UUID (Supplement, Part A, section 1.10) */ - SOLICIT_UUID32 = 0x1F, - - /** Service data, 32-bit UUID (Supplement, Part A, section 1.11) */ - SVC_DATA_UUID32 = 0x20, - /** Service data, 128-bit UUID (Supplement, Part A, section 1.11) */ - SVC_DATA_UUID128 = 0x21, - - /** SSP: LE Secure Connections Confirmation Value (Supplement Part A, Section 1.6) */ - SSP_LE_SEC_CONN_ACK_VALUE = 0x22, - /** SSP: LE Secure Connections Random Value (Supplement Part A, Section 1.6) */ - SSP_LE_SEC_CONN_RND_VALUE = 0x23, - - /* URI (Supplement, Part A, section 1.18) */ - URI = 0x24, - - /* Indoor Positioning - Indoor Positioning Service v1.0 or later */ - INDOOR_POSITIONING = 0x25, - - /* Transport Discovery Data - Transport Discovery Service v1.0 or later */ - TX_DISCOVERY_DATA = 0x26, - - /** LE Supported Features (Supplement, Part A, Section 1.19) */ - LE_SUPP_FEATURES = 0x27, - - CH_MAP_UPDATE_IND = 0x28, - PB_ADV = 0x29, - MESH_MESSAGE = 0x2A, - MESH_BEACON = 0x2B, - BIG_INFO = 0x2C, - BROADCAST_CODE = 0x2D, - INFO_DATA_3D = 0x3D, - - /** Manufacturer id code and specific opaque data */ - MANUFACTURE_SPECIFIC = 0xFF -}; - - -// ************************************************* -// ************************************************* -// ************************************************* - - -/** - * A packed 48 bit EUI-48 identifier, formerly known as MAC-48 - * or simply network device MAC address (Media Access Control address). - */ -struct __attribute__((packed)) EUI48 { - uint8_t b[6]; // == sizeof(EUI48) - - EUI48() { bzero(b, sizeof(EUI48)); } - EUI48(const std::string mac); - EUI48(const EUI48 &o) noexcept = default; - EUI48(EUI48 &&o) noexcept = default; - EUI48& operator=(const EUI48 &o) noexcept = default; - EUI48& operator=(EUI48 &&o) noexcept = default; - - std::string toString() const; -}; - -inline bool operator<(const EUI48& lhs, const EUI48& rhs) -{ return memcmp(&lhs, &rhs, sizeof(EUI48))<0; } - -inline bool operator==(const EUI48& lhs, const EUI48& rhs) -{ return !memcmp(&lhs, &rhs, sizeof(EUI48)); } - -inline bool operator!=(const EUI48& lhs, const EUI48& rhs) -{ return !(lhs == rhs); } - -/** EUI48 MAC address matching any device, i.e. '0:0:0:0:0:0'. */ -static const EUI48 EUI48_ANY_DEVICE; - -// ************************************************* -// ************************************************* -// ************************************************* - - -class ManufactureSpecificData -{ -public: - uint16_t const company; - std::string const companyName; - int const data_len; - std::shared_ptr<uint8_t> const data; - - ManufactureSpecificData() - : company(0), companyName(), data_len(0), data(nullptr) {} - - ManufactureSpecificData(uint16_t const company, uint8_t const * const data, int const data_len); - - std::string getCompanyString() const; - std::string toString() const; -}; - -// ************************************************* -// ************************************************* -// ************************************************* - -/** - * Collection of 'Advertising Data' (AD) - * or 'Extended Inquiry Response' (EIR) information. - */ -class EInfoReport -{ -public: - enum class Source : int { - /** not available */ - NA, - /* Advertising Data (AD) */ - AD, - /** Extended Inquiry Response (EIR) */ - EIR - }; - enum class Element : uint32_t { - EVT_TYPE = (1 << 0), - BDADDR_TYPE = (1 << 1), - BDADDR = (1 << 2), - NAME = (1 << 3), - NAME_SHORT = (1 << 4), - RSSI = (1 << 5), - TX_POWER = (1 << 6), - MANUF_DATA = (1 << 7) - }; - -private: - Source source = Source::NA; - uint64_t timestamp = 0; - uint32_t data_set = 0; - - uint8_t evt_type = 0; - uint8_t mac_type = 0; - EUI48 mac; - - std::string name; - std::string name_short; - int8_t rssi = 0; - int8_t tx_power = 0; - std::shared_ptr<ManufactureSpecificData> msd = nullptr; - std::vector<std::shared_ptr<UUID>> services; - - void set(Element bit) { data_set |= static_cast<uint32_t>(bit); } - void setSource(Source s) { source = s; } - void setTimestamp(uint64_t ts) { timestamp = ts; } - void setEvtType(uint8_t et) { evt_type = et; set(Element::EVT_TYPE); } - void setAddressType(uint8_t at) { mac_type = at; set(Element::BDADDR_TYPE); } - void setAddress(EUI48 const &a) { mac = a; set(Element::BDADDR); } - void setName(const uint8_t *buffer, int buffer_len); - void setShortName(const uint8_t *buffer, int buffer_len); - void setRSSI(int8_t v) { rssi = v; set(Element::RSSI); } - void setTxPower(int8_t v) { tx_power = v; set(Element::TX_POWER); } - void setManufactureSpecificData(uint16_t const company, uint8_t const * const data, int const data_len) { - msd = std::shared_ptr<ManufactureSpecificData>(new ManufactureSpecificData(company, data, data_len)); - set(Element::MANUF_DATA); - } - - void addService(std::shared_ptr<UUID> const &uuid); - - - int 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); - -public: - static bool isSet(const uint32_t data_set, Element bit) { return 0 != (data_set & static_cast<uint32_t>(bit)); } - static std::string dataSetToString(const uint32_t data_Set); - - /** - * Reads a complete Advertising Data (AD) Report - * and returns the number of AD reports in form of a sharable list of EInfoReport; - * <p> - * See Bluetooth Core Specification V5.2 [Vol. 4, Part E, 7.7.65.2, p 2382] - * <p> - * https://www.bluetooth.com/specifications/archived-specifications/ - * </p> - */ - static std::vector<std::shared_ptr<EInfoReport>> read_ad_reports(uint8_t const * data, uint8_t const data_length); - - /** - * Reads the Extended Inquiry Response (EIR) or Advertising Data (AD) segments - * and returns the number of parsed data segments; - * <p> - * AD as well as EIR information is passed in little endian order - * in the same fashion data block: - * <pre> - * a -> { - * uint8_t len - * uint8_t type - * uint8_t data[len-1]; - * } - * b -> next block = a + 1 + len; - * </pre> - * </p> - * <p> - * See Bluetooth Core Specification V5.2 [Vol. 3, Part C, 11, p 1392] - * and Bluetooth Core Specification Supplement V9, Part A: 1, p 9 + 2 Examples, p25.. - * and "Assigned Numbers - Generic Access Profile" - * <https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/> - * </p> - * <p> - * https://www.bluetooth.com/specifications/archived-specifications/ - * </p> - */ - int read_data(uint8_t const * data, uint8_t const data_length); - - Source getSource() const { return source; } - uint64_t getTimestamp() const { return timestamp; } - bool isSet(Element bit) const { return 0 != (data_set & static_cast<uint32_t>(bit)); } - uint32_t getDataSet() const { return data_set; } - - uint8_t getEvtType() const { return evt_type; } - uint8_t getAddressType() const { return mac_type; } - EUI48 const & getAddress() const { return mac; } - std::string const & getName() const { return name; } - std::string const & getShortName() const { return name_short; } - int8_t getRSSI() const { return rssi; } - int8_t getTxPower() const { return tx_power; } - - std::shared_ptr<ManufactureSpecificData> getManufactureSpecificData() const { return msd; } - std::vector<std::shared_ptr<UUID>> getServices() const { return services; } - - std::string getSourceString() const; - std::string getAddressString() const { return mac.toString(); } - std::string dataSetToString() const; - std::string toString() const; -}; - -// ************************************************* -// ************************************************* -// ************************************************* - -} // namespace tinyb_hci - -#endif /* BTDATATYPES_HPP_ */ diff --git a/api/tinyb_hci/HCITypes.hpp b/api/tinyb_hci/HCITypes.hpp deleted file mode 100644 index f9330986..00000000 --- a/api/tinyb_hci/HCITypes.hpp +++ /dev/null @@ -1,371 +0,0 @@ -/* - * 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. - */ - -#ifndef HCITYPES_HPP_ -#define HCITYPES_HPP_ - -#pragma once -#include <cstring> -#include <string> -#include <memory> -#include <cstdint> -#include <vector> - -#include <mutex> -#include <atomic> - -#include "UUID.hpp" -#include "BTDataTypes.hpp" - -#define JAVA_MAIN_PACKAGE "org/tinyb" -#define JAVA_HCI_PACKAGE "tinyb/hci" - -namespace tinyb_hci { - -enum HCI_Event_Types : uint8_t { - LE_Advertising_Report = 0x3E -}; - -enum LE_Address_T : uint8_t { - LE_PUBLIC = 0x00, - LE_RANDOM = 0x01 -}; - -/** -// ************************************************* -// ************************************************* -// ************************************************* - */ - -class HCIObject -{ -protected: - std::mutex lk; - std::atomic_bool valid; - - HCIObject() : valid(true) {} - - bool lock() { - if (valid) { - lk.lock(); - return true; - } else { - return false; - } - } - - void unlock() { - lk.unlock(); - } - -public: - bool isValid() { return valid; } -}; - -// ************************************************* -// ************************************************* -// ************************************************* - -class HCISession; // forward -class HCIDevice; // forward -class HCIDeviceDiscoveryListener; // forward - -class HCIAdapter : public HCIObject -{ -friend class HCISession; - -private: - static int getDefaultDevId(); - static int getDevId(EUI48 &mac); - static int getDevId(const std::string &hcidev); - - static const int to_send_req_poll_ms = 1000; - - /** Returns index >= 0 if found, otherwise -1 */ - static int findDevice(std::vector<std::shared_ptr<HCIDevice>> const & devices, EUI48 const & mac); - - EUI48 mac; - std::string name; - - std::vector<std::shared_ptr<HCISession>> sessions; - std::vector<std::shared_ptr<HCIDevice>> scannedDevices; // all devices scanned - std::vector<std::shared_ptr<HCIDevice>> discoveredDevices; // matching all requirements for export - std::shared_ptr<HCIDeviceDiscoveryListener> deviceDiscoveryListener = nullptr; - - bool validateDevInfo(); - void sessionClosed(HCISession& s); - - bool addScannedDevice(std::shared_ptr<HCIDevice> const &device); - bool addDiscoveredDevice(std::shared_ptr<HCIDevice> const &device); - -protected: - -public: - const int dev_id; - - /** - * Using the default adapter device - */ - HCIAdapter(); - - /** - * @param[in] mac address - */ - HCIAdapter(EUI48 &mac); - - /** - * @param[in] hcidev shall be 'hci[0-9]' - */ - HCIAdapter(const std::string &hcidev); - - /** - * @param[in] dev_id an already identified HCI device id - */ - HCIAdapter(const int dev_id); - - ~HCIAdapter(); - - bool hasDevId() const { return 0 <= dev_id; } - - EUI48 const & getAddress() const { return mac; } - std::string getAddressString() const { return mac.toString(); } - std::string const & getName() const { return name; } - - /** - * Returns a reference to the newly opened session - * if successful, otherwise nullptr is returned. - */ - std::shared_ptr<HCISession> open(); - - // device discovery aka device scanning - - /** - * Replaces the HCIDeviceDiscoveryListener with the given instance, returning the replaced one. - */ - std::shared_ptr<HCIDeviceDiscoveryListener> setDeviceDiscoveryListener(std::shared_ptr<HCIDeviceDiscoveryListener> l); - - /** - * Starts a new discovery session. - * <p> - * Returns true if successful, otherwise false; - * </p> - * <p> - * Default parameter values are chosen for using public address resolution - * and usual discovery intervals etc. - * </p> - */ - bool startDiscovery(HCISession& s, - uint16_t interval=0x0004, uint16_t window=0x0004, - uint8_t own_mac_type=LE_Address_T::LE_PUBLIC); - - /** - * Closes the discovery session. - * @return true if no error, otherwise false. - */ - bool stopDiscovery(HCISession& s); - - /** - * Discovery devices up until 'timeoutMS' in milliseconds - * or until 'waitForDeviceCount' and 'waitForDevice' - * devices matching 'ad_type_req' criteria has been - * reached. - * - * <p> - * 'waitForDeviceCount' is the number of successfully - * scanned devices matching 'ad_type_req' - * before returning if 'timeoutMS' hasn't been reached. - * <br> - * Default value is '1', i.e. wait for only one device. - * A value of <= 0 denotes infinitive, here 'timeoutMS' - * will end the discovery process. - * </p> - * - * <p> - * 'waitForDevice' is a EUI48 denoting a specific device - * to wait for. - * <br> - * Default value is 'EUI48_ANY_DEVICE', i.e. wait for any device. - * </p> - * - * <p> - * 'ad_type_req' is a bitmask of 'EInfoReport::Element' - * denoting required data to be received before - * adding or updating the devices in the discovered list. - * <br> - * Default value is: 'EInfoReport::Element::NAME', - * while 'EInfoReport::Element::BDADDR|EInfoReport::Element::RSSI' is implicit - * and guarantedd by the AD protocol. - * </p> - * - * @return number of successfully scanned devices matching above criteria - * or -1 if an error has occurred. - */ - int discoverDevices(HCISession& s, - const int waitForDeviceCount=1, - const EUI48 &waitForDevice=EUI48_ANY_DEVICE, - const int timeoutMS=to_send_req_poll_ms, - const uint32_t ad_type_req=static_cast<uint32_t>(EInfoReport::Element::NAME)); - - /** Returns discovered devices from a discovery */ - std::vector<std::shared_ptr<HCIDevice>> getDiscoveredDevices() { return discoveredDevices; } - - /** Discards all discovered devices. */ - void removeDiscoveredDevices(); - - /** Returns index >= 0 if found, otherwise -1 */ - int findDiscoveredDevice(EUI48 const & mac) const; - - std::shared_ptr<HCIDevice> getDiscoveredDevice(int index) const { return discoveredDevices.at(index); } - - std::string toString() const; -}; - -// ************************************************* -// ************************************************* -// ************************************************* - -class HCISession -{ -friend class HCIAdapter; - -private: - static std::atomic_int name_counter; - HCIAdapter &adapter; - - /** HCI device handle, open, close etc. dd < 0 is uninitialized */ - int _dd; - - HCISession(HCIAdapter &a, int dd) - : adapter(a), _dd(dd), name(name_counter.fetch_add(1)) - {} - -public: - const int name; - - ~HCISession() { close(); } - - const HCIAdapter &getAdapter() { return adapter; } - - bool close(); - bool isOpen() const { return 0 <= _dd; } - int dd() const { return _dd; } -}; - -inline bool operator<(const HCISession& lhs, const HCISession& rhs) -{ return lhs.name < rhs.name; } - -inline bool operator==(const HCISession& lhs, const HCISession& rhs) -{ return lhs.name == rhs.name; } - -inline bool operator!=(const HCISession& lhs, const HCISession& rhs) -{ return !(lhs == rhs); } - -// ************************************************* -// ************************************************* -// ************************************************* - -class HCIDeviceDiscoveryListener { -public: - virtual void deviceAdded(HCIAdapter const &a, std::shared_ptr<HCIDevice> device) = 0; - virtual void deviceUpdated(HCIAdapter const &a, std::shared_ptr<HCIDevice> device) = 0; - virtual ~HCIDeviceDiscoveryListener() {} -}; - -class HCIDevice : public HCIObject -{ -friend class HCIAdapter; -private: - static const int to_connect_ms = 5000; - - HCIAdapter const & adapter; - uint64_t ts_update; - std::string name; - int8_t rssi = 0; - int8_t tx_power = 0; - std::shared_ptr<ManufactureSpecificData> msd = nullptr; - std::vector<std::shared_ptr<UUID>> services; - - HCIDevice(HCIAdapter const & adapter, EInfoReport const & r); - - void addService(std::shared_ptr<UUID> const &uuid); - void addServices(std::vector<std::shared_ptr<UUID>> const & services); - - void update(EInfoReport const & data); - -public: - const uint64_t ts_creation; - /** Device mac address */ - const EUI48 mac; - - uint64_t getCreationTimestamp() const { return ts_creation; } - uint64_t getUpdateTimestamp() const { return ts_update; } - uint64_t getLastUpdateAge(const uint64_t ts_now) const { return ts_now - ts_update; } - EUI48 const & getAddress() const { return mac; } - std::string getAddressString() const { return mac.toString(); } - std::string const & getName() const { return name; } - bool hasName() const { return name.length()>0; } - int8_t getRSSI() const { return rssi; } - int8_t getTxPower() const { return tx_power; } - std::shared_ptr<ManufactureSpecificData> const getManufactureSpecificData() const { return msd; } - - std::vector<std::shared_ptr<UUID>> getServices() const { return services; } - - /** Returns index >= 0 if found, otherwise -1 */ - int findService(std::shared_ptr<UUID> const &uuid) const; - - std::string toString() const; - - /** - * Creates a new connection to this device. - * <p> - * Returns the new connection handle if successful, otherwise 0 is returned. - * </p> - * <p> - * Default parameter values are chosen for using public address resolution - * and usual connection latency, interval etc. - * </p> - */ - uint16_t le_connect(HCISession& s, - uint16_t interval=0x0004, uint16_t window=0x0004, - uint16_t min_interval=0x000F, uint16_t max_interval=0x000F, - uint16_t latency=0x0000, uint16_t supervision_timeout=0x0C80, - uint16_t min_ce_length=0x0001, uint16_t max_ce_length=0x0001, - uint8_t initiator_filter=0, - uint8_t peer_mac_type=LE_Address_T::LE_PUBLIC, - uint8_t own_mac_type=LE_Address_T::LE_PUBLIC ); -}; - -inline bool operator<(const HCIDevice& lhs, const HCIDevice& rhs) -{ return lhs.mac < rhs.mac; } - -inline bool operator==(const HCIDevice& lhs, const HCIDevice& rhs) -{ return lhs.mac == rhs.mac; } - -inline bool operator!=(const HCIDevice& lhs, const HCIDevice& rhs) -{ return !(lhs == rhs); } - -} // namespace tinyb_hci - -#endif /* HCITYPES_HPP_ */ diff --git a/api/tinyb_hci/UUID.hpp b/api/tinyb_hci/UUID.hpp deleted file mode 100644 index b51c5361..00000000 --- a/api/tinyb_hci/UUID.hpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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. - */ - -#ifndef UUID_HPP_ -#define UUID_HPP_ - -#pragma once -#include <cstring> -#include <string> -#include <memory> -#include <cstdint> -#include <vector> - -#include "BasicTypes.hpp" - -namespace tinyb_hci { - -class UUID128; // forward - -/** - * Bluetooth UUID <https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/> - * <p> - * Bluetooth is LSB or Little-Endian! - * </p> - * <p> - * BASE_UUID '00000000-0000-1000-8000-00805F9B34FB' - * </p> - */ -extern UUID128 BT_BASE_UUID; - -class UUID { -public: - /** Underlying integer value present octet count */ - enum class Type : int { - UUID16=2, UUID32=4, UUID128=16 - }; - Type const type; - UUID(Type const type) : type(type) {} - virtual ~UUID() {}; - - UUID(const UUID &o) noexcept = default; - UUID(UUID &&o) noexcept = default; - UUID& operator=(const UUID &o) noexcept = default; - UUID& operator=(UUID &&o) noexcept = default; - - virtual bool operator==(UUID const &o) const = 0; - virtual bool operator!=(UUID const &o) const = 0; - - Type getType() const { return type; } - virtual std::string toString() const = 0; - virtual std::string toUUID128String(UUID128 const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const = 0; -}; - -class UUID16 : public UUID { -public: - uint16_t value; - - UUID16(uint16_t const v) - : UUID(Type::UUID16), value(v) { } - - UUID16(uint8_t const * const buffer, int const byte_offset, bool littleEndian) - : UUID(Type::UUID16), value(get_uint16(buffer, byte_offset, littleEndian)) { } - - UUID16(const UUID16 &o) noexcept = default; - UUID16(UUID16 &&o) noexcept = default; - UUID16& operator=(const UUID16 &o) noexcept = default; - UUID16& operator=(UUID16 &&o) noexcept = default; - - bool operator==(UUID const &o) const override { - if( this == &o ) { - return true; - } - return type == o.type && value == static_cast<UUID16 const *>(&o)->value; - } - bool operator!=(UUID const &o) const override - { return !(*this == o); } - - std::string toString() const override; - std::string toUUID128String(UUID128 const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override; -}; - -class UUID32 : public UUID { -public: - uint32_t value; - - UUID32(uint32_t const v) - : UUID(Type::UUID32), value(v) {} - - UUID32(uint8_t const * const buffer, int const byte_offset, bool const littleEndian) - : UUID(Type::UUID32), value(get_uint32(buffer, byte_offset, littleEndian)) { } - - UUID32(const UUID32 &o) noexcept = default; - UUID32(UUID32 &&o) noexcept = default; - UUID32& operator=(const UUID32 &o) noexcept = default; - UUID32& operator=(UUID32 &&o) noexcept = default; - - bool operator==(UUID const &o) const override { - if( this == &o ) { - return true; - } - return type == o.type && value == static_cast<UUID32 const *>(&o)->value; - } - bool operator!=(UUID const &o) const override - { return !(*this == o); } - - std::string toString() const override; - std::string toUUID128String(UUID128 const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override; -}; - -class UUID128 : public UUID { -public: - uint128_t value; - - UUID128() : UUID(Type::UUID128) { bzero(value.data, sizeof(value)); } - - UUID128(uint128_t const v) - : UUID(Type::UUID128), value(v) {} - - UUID128(const std::string str); - - UUID128(uint8_t const * const buffer, int const byte_offset, bool const littleEndian) - : UUID(Type::UUID128), value(get_uint128(buffer, byte_offset, littleEndian)) { } - - UUID128(UUID128 const & base_uuid, UUID16 const & uuid16, int const uuid16_le_octet_index); - - UUID128(UUID128 const & base_uuid, UUID32 const & uuid32, int const uuid32_le_octet_index); - - UUID128(const UUID128 &o) noexcept = default; - UUID128(UUID128 &&o) noexcept = default; - UUID128& operator=(const UUID128 &o) noexcept = default; - UUID128& operator=(UUID128 &&o) noexcept = default; - - bool operator==(UUID const &o) const override { - if( this == &o ) { - return true; - } - return type == o.type && value == static_cast<UUID128 const *>(&o)->value; - } - bool operator!=(UUID const &o) const override - { return !(*this == o); } - - std::string toString() const override; - std::string toUUID128String(UUID128 const & base_uuid=BT_BASE_UUID, int const le_octet_index=12) const override { - (void)base_uuid; - (void)le_octet_index; - return toString(); - } -}; - -} /* namespace tinyb_hci */ - -#endif /* UUID_HPP_ */ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 16bfcc00..26edd814 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -5,42 +5,42 @@ include_directories( ${GIO-UNIX_INCLUDE_DIRS} ) -add_executable (hellotinyb hellotinyb.cpp) +add_executable (hellotinyb tinyb/hellotinyb.cpp) set_target_properties(hellotinyb PROPERTIES CXX_STANDARD 11) -add_executable (checkinit checkinit.cpp) +add_executable (checkinit tinyb/checkinit.cpp) set_target_properties(checkinit PROPERTIES CXX_STANDARD 11) -add_executable (asynctinyb asynctinyb.cpp) +add_executable (asynctinyb tinyb/asynctinyb.cpp) set_target_properties(asynctinyb PROPERTIES CXX_STANDARD 11) -add_executable (esstinyb esstinyb.cpp) +add_executable (esstinyb tinyb/esstinyb.cpp) set_target_properties(esstinyb PROPERTIES CXX_STANDARD 11) -add_executable (notifications notifications.cpp) +add_executable (notifications tinyb/notifications.cpp) set_target_properties(notifications PROPERTIES CXX_STANDARD 11) -add_executable (uuid uuid.cpp) +add_executable (uuid tinyb/uuid.cpp) set_target_properties(uuid PROPERTIES CXX_STANDARD 11) -add_executable (list_mfg list_mfg.cpp) +add_executable (list_mfg tinyb/list_mfg.cpp) set_target_properties(list_mfg PROPERTIES CXX_STANDARD 11) -add_executable (hci_scanner HCIScanner.cpp) +add_executable (dbt_scanner direct_bt_scanner/dbt_scanner.cpp) set_target_properties(list_mfg PROPERTIES CXX_STANDARD 11) @@ -54,5 +54,7 @@ target_link_libraries (esstinyb tinyb) target_link_libraries (notifications tinyb) target_link_libraries (uuid tinyb) target_link_libraries (list_mfg tinyb) -target_link_libraries (hci_scanner tinyb_hci) +target_link_libraries (dbt_scanner direct_bt) + +install(TARGETS dbt_scanner RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/examples/HCIScanner.cpp b/examples/HCIScanner.cpp deleted file mode 100644 index 43adf7d2..00000000 --- a/examples/HCIScanner.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 <tinyb_hci/HCITypes.hpp> -#include <cinttypes> - -class DeviceDiscoveryListener : public tinyb_hci::HCIDeviceDiscoveryListener { - void deviceAdded(tinyb_hci::HCIAdapter const &a, std::shared_ptr<tinyb_hci::HCIDevice> device) { - fprintf(stderr, "****** ADDED__: %s\n", device->toString().c_str()); - fprintf(stderr, "Status HCIAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); - } - void deviceUpdated(tinyb_hci::HCIAdapter const &a, std::shared_ptr<tinyb_hci::HCIDevice> device) { - fprintf(stderr, "****** UPDATED: %s\n", device->toString().c_str()); - fprintf(stderr, "Status HCIAdapter:\n"); - fprintf(stderr, "%s\n", a.toString().c_str()); - } -}; - -int main(int argc, char *argv[]) -{ - bool ok = true, done=false; - tinyb_hci::EUI48 waitForDevice = tinyb_hci::EUI48_ANY_DEVICE; - - for(int i=1; i<argc; i++) { - if( !strcmp("-mac", argv[i]) && argc > (i+1) ) { - std::string macstr = std::string(argv[++i]); - waitForDevice = tinyb_hci::EUI48(macstr); - fprintf(stderr, "waitForDevice: %s\n", waitForDevice.toString().c_str()); - } - } - - tinyb_hci::HCIAdapter adapter; // default - if( !adapter.hasDevId() ) { - fprintf(stderr, "Default adapter not available.\n"); - exit(1); - } - if( !adapter.isValid() ) { - fprintf(stderr, "Adapter invalid.\n"); - exit(1); - } - fprintf(stderr, "Adapter: device %s, address %s\n", - adapter.getName().c_str(), adapter.getAddressString().c_str()); - - adapter.setDeviceDiscoveryListener(std::shared_ptr<tinyb_hci::HCIDeviceDiscoveryListener>(new DeviceDiscoveryListener())); - - const int64_t t0 = tinyb_hci::getCurrentMilliseconds(); - - std::shared_ptr<tinyb_hci::HCISession> session = adapter.open(); - - while( ok && !done && nullptr != session ) { - ok = adapter.startDiscovery(*session); - if( !ok) { - fprintf(stderr, "Adapter start discovery failed.\n"); - goto out; - } - - const int deviceCount = adapter.discoverDevices(*session, 1, waitForDevice); - if( 0 > deviceCount ) { - fprintf(stderr, "Adapter discovery failed.\n"); - ok = false; - } - - if( !adapter.stopDiscovery(*session) ) { - fprintf(stderr, "Adapter stop discovery failed.\n"); - ok = false; - } - - if( ok && 0 < deviceCount ) { - const uint64_t t1 = tinyb_hci::getCurrentMilliseconds(); - std::vector<std::shared_ptr<tinyb_hci::HCIDevice>> discoveredDevices = adapter.getDiscoveredDevices(); - int i=0, j=0, k=0; - for(auto it = discoveredDevices.begin(); it != discoveredDevices.end(); it++) { - i++; - std::shared_ptr<tinyb_hci::HCIDevice> device = *it; - const uint64_t lup = device->getLastUpdateAge(t1); - if( 2000 > lup ) { - // less than 2s old .. - j++; - const uint16_t handle = device->le_connect(*session); - if( 0 == handle ) { - fprintf(stderr, "Connection: Failed %s\n", device->toString().c_str()); - } else { - const uint64_t t3 = tinyb_hci::getCurrentMilliseconds(); - const uint64_t td0 = t3 - t0; - const uint64_t td1 = t3 - t1; - fprintf(stderr, "Connection: Success in connect-only %" PRIu64 - " ms, discovered to post-connect %" PRIu64 - " ms, total %" PRIu64 " ms, handle 0x%X\n", - td1, (t3 - device->getCreationTimestamp()), td0, handle); - fprintf(stderr, "Connection: Success to %s\n", device->toString().c_str()); - k++; - done = true; - } - } - } - fprintf(stderr, "Connection: Got %d devices, tried connected to %d with %d succeeded\n", i, j, k); - } - } - -out: - if( nullptr != session ) { - session->close(); - } - return 0; -} - diff --git a/examples/direct_bt_scanner/dbt_scanner.cpp b/examples/direct_bt_scanner/dbt_scanner.cpp new file mode 100644 index 00000000..dc245064 --- /dev/null +++ b/examples/direct_bt_scanner/dbt_scanner.cpp @@ -0,0 +1,311 @@ +/* + * 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 <direct_bt/BTAddress.hpp> +#include <direct_bt/HCITypes.hpp> +#include <direct_bt/ATTPDUTypes.hpp> +#include <direct_bt/GATTHandler.hpp> +#include <direct_bt/GATTNumbers.hpp> +#include <cinttypes> + +extern "C" { + #include <unistd.h> +} + +using namespace direct_bt; + +class DeviceDiscoveryListener : public direct_bt::HCIDeviceDiscoveryListener { + void deviceAdded(direct_bt::HCIAdapter const &a, std::shared_ptr<direct_bt::HCIDevice> device) override { + fprintf(stderr, "****** ADDED__: %s\n", device->toString().c_str()); + fprintf(stderr, "Status HCIAdapter:\n"); + fprintf(stderr, "%s\n", a.toString().c_str()); + } + void deviceUpdated(direct_bt::HCIAdapter const &a, std::shared_ptr<direct_bt::HCIDevice> device) override { + fprintf(stderr, "****** UPDATED: %s\n", device->toString().c_str()); + fprintf(stderr, "Status HCIAdapter:\n"); + fprintf(stderr, "%s\n", a.toString().c_str()); + } +}; + +static const uuid16_t _TEMPERATURE_MEASUREMENT(GattCharacteristicType::TEMPERATURE_MEASUREMENT); + +class MyGATTNotificationListener : public direct_bt::GATTNotificationListener { + void notificationReceived(std::shared_ptr<HCIDevice> dev, + GATTCharacterisicsDeclRef charDecl, std::shared_ptr<const AttHandleValueRcv> charValue) override { + const int64_t tR = direct_bt::getCurrentMilliseconds(); + fprintf(stderr, "****** GATT Notify (td %" PRIu64 " ms, dev-discovered %" PRIu64 " ms): From %s\n", + (tR-charValue->ts_creation), (tR-dev->ts_creation), dev->toString().c_str()); + if( nullptr != charDecl ) { + fprintf(stderr, "****** decl %s\n", charDecl->toString().c_str()); + } + fprintf(stderr, "****** rawv %s\n", charValue->toString().c_str()); + } +}; +class MyGATTIndicationListener : public direct_bt::GATTIndicationListener { + void indicationReceived(std::shared_ptr<HCIDevice> dev, + GATTCharacterisicsDeclRef charDecl, std::shared_ptr<const AttHandleValueRcv> charValue, + const bool confirmationSent) override + { + const int64_t tR = direct_bt::getCurrentMilliseconds(); + fprintf(stderr, "****** GATT Indication (confirmed %d, td(msg %" PRIu64 " ms, dev-discovered %" PRIu64 " ms): From %s\n", + confirmationSent, (tR-charValue->ts_creation), (tR-dev->ts_creation), dev->toString().c_str()); + if( nullptr != charDecl ) { + fprintf(stderr, "****** decl %s\n", charDecl->toString().c_str()); + if( _TEMPERATURE_MEASUREMENT == *charDecl->uuid ) { + std::shared_ptr<TemperatureMeasurementCharateristic> temp = TemperatureMeasurementCharateristic::get(charValue->getValue()); + if( nullptr != temp ) { + fprintf(stderr, "****** valu %s\n", temp->toString().c_str()); + } + } + } + fprintf(stderr, "****** rawv %s\n", charValue->toString().c_str()); + } +}; + +// #define SCAN_CHARACTERISTIC_DESCRIPTORS 1 +// #define SHOW_STATIC_SERVICE_CHARACTERISTIC_COMPOSITION 1 + +int main(int argc, char *argv[]) +{ + bool ok = true, done=false; + bool waitForEnter=false; + EUI48 waitForDevice = EUI48_ANY_DEVICE; + + /** + * BT Core Spec v5.2: Vol 3, Part A L2CAP Spec: 7.9 PRIORITIZING DATA OVER HCI + * + * In order for guaranteed channels to meet their guarantees, + * L2CAP should prioritize traffic over the HCI transport in devices that support HCI. + * Packets for Guaranteed channels should receive higher priority than packets for Best Effort channels. + * ... + * I have noticed that w/o HCI le_connect, overall communication takes twice as long!!! + */ + bool doHCI_LEConnect = true; + + for(int i=1; i<argc; i++) { + if( !strcmp("-wait", argv[i]) ) { + waitForEnter = true; + } else if( !strcmp("-skipLEConnect", argv[i]) ) { + doHCI_LEConnect = false; + } else if( !strcmp("-mac", argv[i]) && argc > (i+1) ) { + std::string macstr = std::string(argv[++i]); + waitForDevice = EUI48(macstr); + fprintf(stderr, "waitForDevice: %s\n", waitForDevice.toString().c_str()); + } + } + + if( waitForEnter ) { + fprintf(stderr, "Press ENTER to continue\n"); + getchar(); + } + + direct_bt::HCIAdapter adapter; // default + if( !adapter.hasDevId() ) { + fprintf(stderr, "Default adapter not available.\n"); + exit(1); + } + if( !adapter.isValid() ) { + fprintf(stderr, "Adapter invalid.\n"); + exit(1); + } + fprintf(stderr, "Adapter: device %s, address %s\n", + adapter.getName().c_str(), adapter.getAddressString().c_str()); + + adapter.setDeviceDiscoveryListener(std::shared_ptr<direct_bt::HCIDeviceDiscoveryListener>(new DeviceDiscoveryListener())); + + const int64_t t0 = direct_bt::getCurrentMilliseconds(); + + std::shared_ptr<direct_bt::HCISession> session = adapter.open(); + + while( ok && !done && nullptr != session ) { + ok = adapter.startDiscovery(*session); + if( !ok) { + perror("Adapter start discovery failed"); + goto out; + } + + const int deviceCount = adapter.discoverDevices(*session, 1, waitForDevice); + if( 0 > deviceCount ) { + perror("Adapter discovery failed"); + ok = false; + } + adapter.stopDiscovery(*session); + + if( ok && 0 < deviceCount ) { + const uint64_t t1 = direct_bt::getCurrentMilliseconds(); + std::vector<std::shared_ptr<direct_bt::HCIDevice>> discoveredDevices = adapter.getDiscoveredDevices(); + int i=0, j=0, k=0; + for(auto it = discoveredDevices.begin(); it != discoveredDevices.end(); it++) { + i++; + std::shared_ptr<direct_bt::HCIDevice> device = *it; + const uint64_t lup = device->getLastUpdateAge(t1); + if( 2000 > lup ) { + // less than 2s old .. + j++; + if( waitForDevice == device->getAddress() ) { + done = true; + } + + // + // HCI LE-Connect + // (Without: Overall communication takes ~twice as long!!!) + // + uint16_t hciLEConnHandle; + if( doHCI_LEConnect ) { + hciLEConnHandle = device->le_connect(*session); + if( 0 == hciLEConnHandle ) { + fprintf(stderr, "HCI LE Connection: Failed %s\n", device->toString().c_str()); + } else { + const uint64_t t3 = direct_bt::getCurrentMilliseconds(); + const uint64_t td0 = t3 - t0; + const uint64_t td1 = t3 - t1; + fprintf(stderr, "HCI LE Connect: Success\n"); + fprintf(stderr, " hci connect-only %" PRIu64 " ms,\n" + " discovered to hci-connected %" PRIu64 " ms,\n" + " total %" PRIu64 " ms,\n" + " handle 0x%X\n", + td1, (t3 - device->getCreationTimestamp()), td0, hciLEConnHandle); + } + } else { + fprintf(stderr, "HCI LE Connection: Skipped %s\n", device->toString().c_str()); + hciLEConnHandle = 0; + } + + k++; + + // + // GATT Processing + // + const uint64_t t4 = direct_bt::getCurrentMilliseconds(); + // let's check further for full GATT + direct_bt::GATTHandler gatt(device, 10000); + if( gatt.connect() ) { + fprintf(stderr, "GATT usedMTU %d (server) -> %d (used)\n", gatt.getServerMTU(), gatt.getUsedMTU()); + + gatt.setGATTIndicationListener(std::shared_ptr<GATTIndicationListener>(new MyGATTIndicationListener()), true /* sendConfirmation */); + gatt.setGATTNotificationListener(std::shared_ptr<GATTNotificationListener>(new MyGATTNotificationListener())); + +#ifdef SCAN_CHARACTERISTIC_DESCRIPTORS + std::vector<std::vector<GATTUUIDHandle>> servicesCharacteristicDescriptors; +#endif + std::vector<GATTPrimaryServiceRef> & primServices = gatt.discoverCompletePrimaryServices(); + const uint64_t t5 = direct_bt::getCurrentMilliseconds(); +#ifdef SCAN_CHARACTERISTIC_DESCRIPTORS + for(size_t i=0; i<primServices.size(); i++) { + std::vector<GATTUUIDHandle> serviceDescriptors; + gatt.discoverCharDescriptors(primServices.at(i), serviceDescriptors); + servicesCharacteristicDescriptors.push_back(serviceDescriptors); + } +#endif + const uint64_t t7 = direct_bt::getCurrentMilliseconds(); + { + const uint64_t td45 = t5 - t4; // connect -> complete primary services + const uint64_t td47 = t7 - t4; // connect -> gatt complete + const uint64_t td07 = t7 - t0; // total + fprintf(stderr, "\n\n\n"); + fprintf(stderr, "GATT primary-services completed\n"); + fprintf(stderr, " gatt connect -> complete primary-services %" PRIu64 " ms,\n" + " gatt connect -> gatt complete %" PRIu64 " ms,\n" + " discovered to gatt complete %" PRIu64 " ms,\n" + " total %" PRIu64 " ms\n\n", + td45, td47, (t7 - device->getCreationTimestamp()), td07); + } + { + std::shared_ptr<GenericAccess> ga = gatt.getGenericAccess(primServices); + if( nullptr != ga ) { + fprintf(stderr, " GenericAccess: %s\n\n", ga->toString().c_str()); + } + } + { + std::shared_ptr<DeviceInformation> di = gatt.getDeviceInformation(primServices); + if( nullptr != di ) { + fprintf(stderr, " DeviceInformation: %s\n\n", di->toString().c_str()); + } + } + + for(size_t i=0; i<primServices.size(); i++) { + GATTPrimaryService & primService = *primServices.at(i); + fprintf(stderr, " [%2.2d] Service %s\n", (int)i, primService.toString().c_str()); + fprintf(stderr, " [%2.2d] Service Characteristics\n", (int)i); + std::vector<GATTCharacterisicsDeclRef> & serviceCharacteristics = primService.characteristicDeclList; + for(size_t j=0; j<serviceCharacteristics.size(); j++) { + GATTCharacterisicsDecl & serviceChar = *serviceCharacteristics.at(j); + fprintf(stderr, " [%2.2d.%2.2d] Decla: %s\n", (int)i, (int)j, serviceChar.toString().c_str()); + if( serviceChar.hasProperties(GATTCharacterisicsDecl::PropertyBitVal::Read) ) { + POctets value(GATTHandler::ClientMaxMTU, 0); + if( gatt.readCharacteristicValue(serviceChar, value) ) { + fprintf(stderr, " [%2.2d.%2.2d] Value: %s\n", (int)i, (int)j, value.toString().c_str()); + } + } + if( nullptr != serviceChar.config ) { + const bool enableNotification = serviceChar.hasProperties(GATTCharacterisicsDecl::PropertyBitVal::Notify); + const bool enableIndication = serviceChar.hasProperties(GATTCharacterisicsDecl::PropertyBitVal::Indicate); + if( enableNotification || enableIndication ) { + bool res = gatt.configIndicationNotification(*serviceChar.config, enableNotification, enableIndication); + fprintf(stderr, " [%2.2d.%2.2d] Config Notification(%d), Indication(%d): Result %d\n", + (int)i, (int)j, enableNotification, enableIndication, res); + } + } + } +#ifdef SCAN_CHARACTERISTIC_DESCRIPTORS + fprintf(stderr, " [%2.2d] Service Characteristics Descriptors\n", (int)i); + std::vector<GATTUUIDHandle> serviceDescriptors = servicesCharacteristicDescriptors.at(i); + for(size_t j=0; j<serviceDescriptors.size(); j++) { + fprintf(stderr, " [%2.2d.%2.2d] %s\n", (int)i, (int)j, serviceDescriptors.at(j).toString().c_str()); + } +#endif + } + // FIXME sleep 2s for potential callbacks .. + sleep(2); + gatt.disconnect(); + } else { + fprintf(stderr, "GATT connect failed: %s\n", gatt.getStateString().c_str()); + } + if( 0 != hciLEConnHandle ) { + session->disconnect(0); // FIXME: hci_le_disconnect: Input/output error + } + } // if( 2000 > lup ) + } // for(auto it = discoveredDevices.begin(); it != discoveredDevices.end(); it++) + fprintf(stderr, "Connection: Got %d devices, tried connected to %d with %d succeeded\n", i, j, k); + } + } + +#ifdef SHOW_STATIC_SERVICE_CHARACTERISTIC_COMPOSITION + // + // Show static composition of Services and Characteristics + // + for(size_t i=0; i<GATT_SERVICES.size(); i++) { + const GattServiceCharacteristic * gsc = GATT_SERVICES.at(i); + fprintf(stderr, "GattServiceCharacteristic %d: %s\n", (int)i, gsc->toString().c_str()); + } +#endif /* SHOW_STATIC_SERVICE_CHARACTERISTIC_COMPOSITION */ + +out: + if( nullptr != session ) { + session->close(); + } + return 0; +} + diff --git a/examples/TinyBTest01.cpp b/examples/tinyb/TinyBTest01.cpp index c50ad776..c50ad776 100644 --- a/examples/TinyBTest01.cpp +++ b/examples/tinyb/TinyBTest01.cpp diff --git a/examples/asynctinyb.cpp b/examples/tinyb/asynctinyb.cpp index f2d5756c..f2d5756c 100644 --- a/examples/asynctinyb.cpp +++ b/examples/tinyb/asynctinyb.cpp diff --git a/examples/checkinit.cpp b/examples/tinyb/checkinit.cpp index fe4d47dd..fe4d47dd 100644 --- a/examples/checkinit.cpp +++ b/examples/tinyb/checkinit.cpp diff --git a/examples/esstinyb.cpp b/examples/tinyb/esstinyb.cpp index 2155a5ed..2155a5ed 100644 --- a/examples/esstinyb.cpp +++ b/examples/tinyb/esstinyb.cpp diff --git a/examples/hellotinyb.cpp b/examples/tinyb/hellotinyb.cpp index 49886195..49886195 100644 --- a/examples/hellotinyb.cpp +++ b/examples/tinyb/hellotinyb.cpp diff --git a/examples/list_mfg.cpp b/examples/tinyb/list_mfg.cpp index 5f298ffb..5f298ffb 100644 --- a/examples/list_mfg.cpp +++ b/examples/tinyb/list_mfg.cpp diff --git a/examples/notifications.cpp b/examples/tinyb/notifications.cpp index e1850ffa..e1850ffa 100644 --- a/examples/notifications.cpp +++ b/examples/tinyb/notifications.cpp diff --git a/examples/uuid.cpp b/examples/tinyb/uuid.cpp index bef2f4cb..bef2f4cb 100644 --- a/examples/uuid.cpp +++ b/examples/tinyb/uuid.cpp diff --git a/include/cppunit/cppunit.h b/include/cppunit/cppunit.h new file mode 100644 index 00000000..af05acc9 --- /dev/null +++ b/include/cppunit/cppunit.h @@ -0,0 +1,110 @@ +#ifndef CPPUNIT_H +#define CPPUNIT_H + +// Required headers, or just use #include <bits/stdc++.h> +#include <iostream> +#include <sstream> +#include <cstring> +#include <string> +#include <ctime> +#include <cmath> + + +// CPlusPlusUnit - C++ Unit testing TDD framework (github.com/cppunit/cppunit) +class Cppunit { + + private: + static float machineFloatEpsilon() { + float x = 1.0f, res; + do { + res = x; + } while (1.0f + (x /= 2.0f) > 1.0f); + return res; + } + + static double machineDoubleEpsilon() { + double x = 1.0, res; + do { + res = x; + } while (1.0 + (x /= 2.0) > 1.0); + return res; + } + + public: + + #define PRINTM(m) print(m, __FILE__, __LINE__, __FUNCTION__); + #define CHECK(a,b) check<long long>("", a, b, #a, #b, __FILE__, __LINE__, __FUNCTION__); + #define CHECKM(m,a,b) check<long long>(m, a, b, #a, #b, __FILE__, __LINE__, __FUNCTION__); + #define CHECKD(m,a,b) checkDelta<double>(m, a, b, doubleEpsilon, #a, #b, __FILE__, __LINE__, __FUNCTION__); + #define CHECKDD(m,a,b,c) checkDelta<double>(m, a, b, c, #a, #b, __FILE__, __LINE__, __FUNCTION__); + #define CHECKT(a) check<bool>("", a, true, #a, "true", __FILE__, __LINE__, __FUNCTION__); + #define CHECKTM(m,a) check<bool>(m, a, true, #a, "true", __FILE__, __LINE__, __FUNCTION__); + #define CHECKS(a,b) check<cs>("", a, b, #a, #b, __FILE__, __LINE__, __FUNCTION__); + #define CHECKSM(m,a,b) check<cs>(m, a, b, #a, #b, __FILE__, __LINE__, __FUNCTION__); + + typedef const std::string& cs; + + int checks, fails; std::ostringstream serr; std::istringstream *in; + float floatEpsilon; + double doubleEpsilon; + + Cppunit() + : checks(0), fails(0), floatEpsilon(machineFloatEpsilon()), doubleEpsilon(machineDoubleEpsilon()) + {} + + virtual ~Cppunit() {} + + void test_cin(cs s){ in = new std::istringstream(s); std::cin.rdbuf(in->rdbuf()); } + + void fail_hdr(cs stra, cs strb, cs file, int line, cs func) { + serr << "==================================================" << std::endl; + serr << "FAIL: " << func << std::endl; + serr << "--------------------------------------------------" << std::endl; + serr << "File \"" << file << "\", line " << line << " in " << func << std::endl; + serr << " Checking " << stra << " == " << strb << std::endl; + } + + void print(cs m, cs file, int line, cs func) { + std::cerr << std::endl << m << "; file \"" << file << "\", line " << line << " in " << func << std::endl; + } + + template <typename T> void check(cs m, T a, T b, cs stra, cs strb, cs file, int line, cs func) { + checks++; if (a == b) { std::cout << "."; return; } + fails++; std::cout << "F"; fail_hdr(stra, strb, file, line, func); + serr << " Error: " << m << ": \"" << a << "\" ! = \"" << b << "\"" << std::endl << std::endl; + } + + template <typename T> void checkDelta(cs m, T a, T b, T d, cs stra, cs strb, cs file, int line, cs func) { + checks++; if ( labs ( a - b ) < d ) { std::cout << "."; return; } + fails++; std::cout << "F"; fail_hdr(stra, strb, file, line, func); + serr << " Error: " << m << ": \"" << a << "\" ! = \"" << b << "\" (delta " << d << ")" << std::endl << std::endl; + } + + virtual void single_test() {} + virtual void test_list() { single_test(); } + double dclock() { return double(clock()) / CLOCKS_PER_SEC; } + int status() { + std::cout << std::endl; if (fails) std::cout << serr.str(); + std::cout << "--------------------------------------------------" << std::endl; + std::cout << "Ran " << checks << " checks in " << dclock() << "s" << std::endl << std::endl; + if (fails) std::cout << "FAILED (failures=" << fails << ")"; else std::cout << "OK" << std::endl; + return fails > 0; + } + int run() { std::streambuf* ocin = std::cin.rdbuf(); test_list(); std::cin.rdbuf(ocin); return status(); } +}; + +template<> void Cppunit::checkDelta<float>(cs m, float a, float b, float epsilon, cs stra, cs strb, cs file, int line, cs func) { + checks++; if ( fabsf( a - b ) < epsilon ) { std::cout << "."; return; } + fails++; std::cout << "F"; fail_hdr(stra, strb, file, line, func); + serr << " Error: " << m << ": \"" << a << "\" ! = \"" << b << "\" (epsilon " << epsilon << ")" << std::endl << std::endl; +} + +template<> void Cppunit::checkDelta<double>(cs m, double a, double b, double epsilon, cs stra, cs strb, cs file, int line, cs func) { + checks++; if ( fabsf( a - b ) < epsilon ) { std::cout << "."; return; } + fails++; std::cout << "F"; fail_hdr(stra, strb, file, line, func); + serr << " Error: " << m << ": \"" << a << "\" ! = \"" << b << "\" (epsilon " << epsilon << ")" << std::endl << std::endl; +} + + +#endif // CPPUNIT_H + diff --git a/java/jni/CMakeLists.txt b/java/jni/CMakeLists.txt index dc613084..cd715a5a 100644 --- a/java/jni/CMakeLists.txt +++ b/java/jni/CMakeLists.txt @@ -8,7 +8,7 @@ endif (JNI_FOUND) set (tinyb_LIB_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/api ${PROJECT_SOURCE_DIR}/api/tinyb - ${PROJECT_SOURCE_DIR}/api/tinyb_hci + ${PROJECT_SOURCE_DIR}/api/direct_bt ${PROJECT_SOURCE_DIR}/include ) diff --git a/java/jni/HCIAdapter.cxx b/java/jni/HCIAdapter.cxx index d357483d..afb420fc 100644 --- a/java/jni/HCIAdapter.cxx +++ b/java/jni/HCIAdapter.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIAdapter.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/java/jni/HCIDevice.cxx b/java/jni/HCIDevice.cxx index d2fb2f98..d143b85d 100644 --- a/java/jni/HCIDevice.cxx +++ b/java/jni/HCIDevice.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIDevice.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/java/jni/HCIGattCharacteristic.cxx b/java/jni/HCIGattCharacteristic.cxx index 7237ffde..0a0c4c07 100644 --- a/java/jni/HCIGattCharacteristic.cxx +++ b/java/jni/HCIGattCharacteristic.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIGattCharacteristic.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/java/jni/HCIGattDescriptor.cxx b/java/jni/HCIGattDescriptor.cxx index 5060170b..cefc6b4d 100644 --- a/java/jni/HCIGattDescriptor.cxx +++ b/java/jni/HCIGattDescriptor.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIGattDescriptor.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/java/jni/HCIGattService.cxx b/java/jni/HCIGattService.cxx index 7ad8d822..14dfcbe8 100644 --- a/java/jni/HCIGattService.cxx +++ b/java/jni/HCIGattService.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIGattService.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/java/jni/HCIManager.cxx b/java/jni/HCIManager.cxx index 218e98d7..57865644 100644 --- a/java/jni/HCIManager.cxx +++ b/java/jni/HCIManager.cxx @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "JNIMem.hpp" #include "helper_base.hpp" diff --git a/java/jni/HCIObject.cxx b/java/jni/HCIObject.cxx index 0f261eba..1f420eea 100644 --- a/java/jni/HCIObject.cxx +++ b/java/jni/HCIObject.cxx @@ -23,12 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "tinyb_hci/HCITypes.hpp" +#include "direct_bt/HCITypes.hpp" #include "tinyb_hci_HCIObject.h" #include "JNIMem.hpp" #include "helper_base.hpp" -using namespace tinyb_hci; +using namespace direct_bt; diff --git a/scripts/build-armhf.sh b/scripts/build-armhf.sh index 7dd4ffdd..78eaee98 100755 --- a/scripts/build-armhf.sh +++ b/scripts/build-armhf.sh @@ -13,7 +13,10 @@ rm -rf build-armhf mkdir build-armhf cd build-armhf cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-armhf -DBUILDJAVA=ON -DBUILDEXAMPLES=ON .. +# cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-armhf -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON .. +cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-armhf -DBUILDEXAMPLES=ON -DDEBUG=ON -DBUILD_TESTING=ON .. make +make test make install cp -a examples/* $rootdir/dist-armhf/bin diff --git a/scripts/build-x86_64.sh b/scripts/build-x86_64.sh index 08649c9d..12d56130 100755 --- a/scripts/build-x86_64.sh +++ b/scripts/build-x86_64.sh @@ -12,8 +12,10 @@ mkdir -p dist-x86_64/bin rm -rf build-x86_64 mkdir build-x86_64 cd build-x86_64 -cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-x86_64 -DBUILDJAVA=ON -DBUILDEXAMPLES=ON .. +# cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-x86_64 -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON .. +cmake -DCMAKE_INSTALL_PREFIX=$rootdir/dist-x86_64 -DBUILDEXAMPLES=ON -DDEBUG=ON -DBUILD_TESTING=ON .. make +make test make install cp -a examples/* $rootdir/dist-x86_64/bin diff --git a/scripts/run-dbt_scanner.sh b/scripts/run-dbt_scanner.sh new file mode 100755 index 00000000..c69b4343 --- /dev/null +++ b/scripts/run-dbt_scanner.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -e bin/dbt_scanner -o ! -e lib/libtinyb.so -o ! -e lib/libdirect_bt.so ] ; then + echo run from dist directory + exit 1 +fi +# hciconfig hci0 reset +#LD_LIBRARY_PATH=`pwd`/lib strace bin/dbt_scanner $* +LD_LIBRARY_PATH=`pwd`/lib bin/dbt_scanner $* diff --git a/scripts/run-hci_scanner.sh b/scripts/run-hci_scanner.sh deleted file mode 100755 index f6c2c1dc..00000000 --- a/scripts/run-hci_scanner.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -if [ ! -e bin/hci_scanner -o ! -e lib/libtinyb.so -o ! -e lib/libtinyb_hci.so ] ; then - echo run from dist directory - exit 1 -fi -# hciconfig hci0 reset -LD_LIBRARY_PATH=`pwd`/lib bin/hci_scanner $* diff --git a/scripts/run-java-scanner.sh b/scripts/run-java-scanner.sh index eb214d60..d4f40875 100755 --- a/scripts/run-java-scanner.sh +++ b/scripts/run-java-scanner.sh @@ -1,6 +1,6 @@ #!/bin/sh -if [ ! -e lib/java/tinyb2.jar -o ! -e bin/java/ScannerTinyB.jar -o ! -e lib/libtinyb.so -o ! -e lib/libtinyb_hci.so ] ; then +if [ ! -e lib/java/tinyb2.jar -o ! -e bin/java/ScannerTinyB.jar -o ! -e lib/libtinyb.so -o ! -e lib/libdirect_bt.so ] ; then echo run from dist directory exit 1 fi diff --git a/scripts/sloccount.sh b/scripts/sloccount.sh new file mode 100755 index 00000000..c994819a --- /dev/null +++ b/scripts/sloccount.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +rm -rf sloccount_direct_bt +mkdir -p sloccount_direct_bt +cd sloccount_direct_bt +ln -s ../api/direct_bt api +ln -s ../include/cppunit cppunit +ln -s ../src/direct_bt src +ln -s ../test/direct_bt test +cd .. + +sloccount --follow --personcost 100000 --overhead 1.30 sloccount_direct_bt 2>&1 | tee sloccount_direct_bt-`date +%Y%m%d`.log 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/tinyb_hci/BTDataTypes.cpp b/src/direct_bt/BTTypes.cpp index f17aa9da..bfd955f6 100644 --- a/src/tinyb_hci/BTDataTypes.cpp +++ b/src/direct_bt/BTTypes.cpp @@ -32,44 +32,22 @@ #include <algorithm> -#include "BTDataTypes.hpp" +#include "BTTypes.hpp" -/** - * TODO libbluetooth replacement: - * - bt_compidtostr - */ - -#define VERBOSE_ON 1 - -#ifdef VERBOSE_ON - #define DBG_PRINT(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) -#else - #define DBG_PRINT(...) -#endif - - -using namespace tinyb_hci; - -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); -} +#include "dbt_debug.hpp" std::string EUI48::toString() const { - char cstr[17+1]; - - const int count = sprintf(cstr, "%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( 17 != count ) { - std::string msg("EUI48 string not of length 17 but "); - msg.append(std::to_string(count)); - throw InternalError(msg, E_FILE_LINE); + 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 std::string(cstr); + return str; } EUI48::EUI48(const std::string str) { @@ -77,25 +55,43 @@ EUI48::EUI48(const std::string str) { std::string msg("EUI48 string not of length 17 but "); msg.append(std::to_string(str.length())); msg.append(": "+str); - throw IllegalArgumentException(msg, E_FILE_LINE); + 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 IllegalArgumentException(msg, E_FILE_LINE); + 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); @@ -131,10 +127,10 @@ void EInfoReport::setShortName(const uint8_t *buffer, int buffer_len) { set(Element::NAME_SHORT); } -void EInfoReport::addService(std::shared_ptr<UUID> const &uuid) +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> const& p) { + auto it = std::find_if(begin, services.end(), [&](std::shared_ptr<uuid_t> const& p) { return *p == *uuid; }); if ( it == std::end(services) ) { @@ -179,8 +175,8 @@ std::string EInfoReport::toString() const { if(services.size() > 0 ) { out.append("\n"); for(auto it = services.begin(); it != services.end(); it++) { - std::shared_ptr<UUID> p = *it; - out.append(" ").append(p->toUUID128String()).append(", ").append(std::to_string(static_cast<int>(p->type))).append(" bytes\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\n"); } } return out; @@ -221,7 +217,7 @@ int EInfoReport::read_data(uint8_t const * data, uint8_t const data_length) { 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\n", + 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++; @@ -233,21 +229,21 @@ int EInfoReport::read_data(uint8_t const * data, uint8_t const data_length) { case GAP_T::UUID16_INCOMPLETE: case GAP_T::UUID16_COMPLETE: for(int j=0; j<elem_len/2; j++) { - const std::shared_ptr<UUID> uuid(new UUID16(elem_data, j*2, true)); + 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> uuid(new UUID32(elem_data, j*4, true)); + 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> uuid(new UUID128(elem_data, j*16, true)); + const std::shared_ptr<uuid_t> uuid(new uuid128_t(elem_data, j*16, true)); addService(std::move(uuid)); } break; @@ -296,7 +292,7 @@ std::vector<std::shared_ptr<EInfoReport>> EInfoReport::read_ad_reports(uint8_t c std::vector<std::shared_ptr<EInfoReport>> ad_reports; if( 0 >= num_reports || num_reports > 0x19 ) { - DBG_PRINT("AD-Reports: Invalid reports count: %d\n", num_reports); + DBG_PRINT("AD-Reports: Invalid reports count: %d", num_reports); return ad_reports; } uint8_t const *limes = data + data_length; @@ -342,7 +338,7 @@ std::vector<std::shared_ptr<EInfoReport>> EInfoReport::read_ad_reports(uint8_t c 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\n", + 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/tinyb_hci/HCIAdapter.cpp b/src/direct_bt/HCIAdapter.cpp index 38e28cd4..b1baeead 100644 --- a/src/tinyb_hci/HCIAdapter.cpp +++ b/src/direct_bt/HCIAdapter.cpp @@ -30,34 +30,23 @@ #include <vector> #include <cstdio> -#include <algorithm> +#include <algorithm> -#include <inttypes.h> -#include <unistd.h> -#include <poll.h> - -extern "C" { - #include <bluetooth/bluetooth.h> - #include <bluetooth/hci.h> - #include <bluetooth/hci_lib.h> -} +#include "BTIoctl.hpp" +#include "HCIIoctl.hpp" +#include "HCIComm.hpp" #include "HCITypes.hpp" -#define VERBOSE_ON 1 - -#ifdef VERBOSE_ON - #define DBG_PRINT(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) -#else - #define DBG_PRINT(...) -#endif - -using namespace tinyb_hci; - -static inline bdaddr_t* eui48_to_bdaddr_ptr(EUI48 *p) { - return static_cast<bdaddr_t *>( static_cast<void *>( p ) ); +extern "C" { + #include <inttypes.h> + #include <unistd.h> + #include <poll.h> } +#include "dbt_debug.hpp" + +using namespace direct_bt; // ************************************************* // ************************************************* @@ -65,13 +54,23 @@ static inline bdaddr_t* eui48_to_bdaddr_ptr(EUI48 *p) { 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( 0 > _dd ) { + if( !hciComm.isOpen() ) { return false; } - hci_close_dev(_dd); - _dd = -1; + hciComm.close(); adapter.sessionClosed(*this); return true; } @@ -80,60 +79,34 @@ bool HCISession::close() // ************************************************* // ************************************************* -int HCIAdapter::getDefaultDevId() { - return hci_get_route(NULL); -} -int HCIAdapter::getDevId(EUI48 &mac) { - return hci_get_route( eui48_to_bdaddr_ptr( &mac ) ); -} -int HCIAdapter::getDevId(const std::string &hcidev) { - return hci_devid(hcidev.c_str()); -} - bool HCIAdapter::validateDevInfo() { - if( 0 > dev_id ) { - return false; - } - struct hci_dev_info dev_info; - bzero(&dev_info, sizeof(dev_info)); - if( 0 > hci_devinfo(dev_id, &dev_info) ) { + if( !mgmt.isOpen() || 0 > dev_id ) { return false; } - memcpy(&mac, &dev_info.bdaddr, sizeof(mac)); - name = std::string(dev_info.name); + + adapterInfo = mgmt.getAdapter(dev_id); return true; } void HCIAdapter::sessionClosed(HCISession& s) { - auto it = std::find_if(sessions.begin(), sessions.end(), [&](std::shared_ptr<HCISession> const& p) { - return *p == s; - }); - if ( it != std::end(sessions) ) { - sessions.erase(it); - } + session = nullptr; } HCIAdapter::HCIAdapter() -: dev_id(getDefaultDevId()) +: mgmt(MgmtHandler::get()), dev_id(mgmt.getDefaultAdapterIdx()) { valid = validateDevInfo(); } HCIAdapter::HCIAdapter(EUI48 &mac) -: dev_id(getDevId(mac)) -{ - valid = validateDevInfo(); -} - -HCIAdapter::HCIAdapter(const std::string &hcidev) -: dev_id(getDevId(hcidev)) +: mgmt(MgmtHandler::get()), dev_id(mgmt.findAdapterIdx(mac)) { valid = validateDevInfo(); } HCIAdapter::HCIAdapter(const int dev_id) -: dev_id(dev_id) +: mgmt(MgmtHandler::get()), dev_id(dev_id) { valid = validateDevInfo(); } @@ -141,7 +114,7 @@ HCIAdapter::HCIAdapter(const int dev_id) HCIAdapter::~HCIAdapter() { scannedDevices.clear(); discoveredDevices.clear(); - sessions.clear(); + session = nullptr; } std::shared_ptr<HCISession> HCIAdapter::open() @@ -149,14 +122,14 @@ std::shared_ptr<HCISession> HCIAdapter::open() if( !valid ) { return nullptr; } - int dd = hci_open_dev(dev_id); - if( 0 > dd ) { + HCISession * s = new HCISession( *this, dev_id, HCI_CHANNEL_RAW ); + if( !s->isOpen() ) { + delete s; perror("Could not open device"); return nullptr; } - std::shared_ptr<HCISession> s(new HCISession(*this, dd)); - sessions.push_back(s); - return s; + session = std::shared_ptr<HCISession>( s ); + return session; } std::shared_ptr<HCIDeviceDiscoveryListener> HCIAdapter::setDeviceDiscoveryListener(std::shared_ptr<HCIDeviceDiscoveryListener> l) @@ -166,50 +139,25 @@ std::shared_ptr<HCIDeviceDiscoveryListener> HCIAdapter::setDeviceDiscoveryListen return o; } -bool HCIAdapter::startDiscovery(HCISession &s, - uint16_t interval, uint16_t window, - uint8_t own_mac_type) +bool HCIAdapter::startDiscovery(HCISession &session, uint8_t own_mac_type, + uint16_t interval, uint16_t window) { - const uint8_t scan_type = 0x01; - const uint8_t filter_type = 0; - const uint8_t filter_policy = 0x00; - const uint8_t filter_dup = 0x01; - - if( !s.isOpen() ) { + if( !session.isOpen() ) { fprintf(stderr, "Session not open\n"); return false; } - int err = hci_le_set_scan_parameters(s.dd(), scan_type, - cpu_to_le(interval), cpu_to_le(window), - own_mac_type, filter_policy, to_send_req_poll_ms); - if (err < 0) { - perror("Set scan parameters failed"); - return false; - } - - err = hci_le_set_scan_enable(s.dd(), 0x01, filter_dup, to_send_req_poll_ms); - if (err < 0) { - perror("Start scan failed"); + if( !session.hciComm.le_enable_scan(own_mac_type, interval, window) ) { + perror("Start scanning failed"); return false; } return true; } -bool HCIAdapter::stopDiscovery(HCISession& session) { - const uint8_t filter_dup = 0x01; - +void HCIAdapter::stopDiscovery(HCISession& session) { if( !session.isOpen() ) { - fprintf(stderr, "Session not open\n"); - return false; - } - bool res; - if( 0 > hci_le_set_scan_enable(session.dd(), 0x00, filter_dup, to_send_req_poll_ms) ) { - perror("Stop scan failed"); - res = false; - } else { - res = true; + return; } - return res; + session.hciComm.le_disable_scan(); } int HCIAdapter::findDevice(std::vector<std::shared_ptr<HCIDevice>> const & devices, EUI48 const & mac) { @@ -224,6 +172,18 @@ int HCIAdapter::findDevice(std::vector<std::shared_ptr<HCIDevice>> const & devic } } +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); @@ -232,18 +192,26 @@ bool HCIAdapter::addScannedDevice(std::shared_ptr<HCIDevice> const &device) { 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 > findDiscoveredDevice(device->mac) ) { + if( 0 > findDiscoveredDeviceIdx(device->mac) ) { discoveredDevices.push_back(device); return true; } return false; } -int HCIAdapter::findDiscoveredDevice(EUI48 const & mac) const { - return findDevice(discoveredDevices, mac); -} - void HCIAdapter::removeDiscoveredDevices() { // also need to flush scannedDevices, old data scannedDevices.clear(); @@ -271,10 +239,11 @@ int HCIAdapter::discoverDevices(HCISession& session, const uint32_t ad_type_req) { uint8_t buf[HCI_MAX_EVENT_SIZE]; - struct hci_filter nf, of; + 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"); @@ -283,16 +252,16 @@ int HCIAdapter::discoverDevices(HCISession& session, olen = sizeof(of); if (getsockopt(session.dd(), SOL_HCI, HCI_FILTER, &of, &olen) < 0) { - fprintf(stderr, "Could not get socket options\n"); + perror("Could not get socket options"); return false; } - hci_filter_clear(&nf); - hci_filter_set_ptype(HCI_EVENT_PKT, &nf); - hci_filter_set_event(EVT_LE_META_EVENT, &nf); + 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) { - fprintf(stderr, "Could not set socket options\n"); + perror("Could not set socket options"); return false; } @@ -306,7 +275,7 @@ int HCIAdapter::discoverDevices(HCISession& session, while ( !done && ( ( t1 = getCurrentMilliseconds() ) - t0 ) < timeoutMS ) { uint8_t hci_type; hci_event_hdr *ehdr; - evt_le_meta_event *meta; + hci_ev_le_meta *meta; loop++; if( timeoutMS ) { @@ -322,7 +291,10 @@ int HCIAdapter::discoverDevices(HCISession& session, goto errout; } if (!n) { - goto done; // timeout: exit but no error + // A timeout is not considered an error for discovery. + // errno = ETIMEDOUT; + // goto errout; + goto done; } } @@ -334,16 +306,16 @@ int HCIAdapter::discoverDevices(HCISession& session, goto errout; } - if( bytes_left < HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE + EVT_LE_META_EVENT_SIZE ) { + 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_SIZE + ehdr = (hci_event_hdr*)(void*) ( buf + HCI_TYPE_LEN ); // sizeof hci_event_hdr bytes_left -= (HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE); - meta = (evt_le_meta_event*)(void *) ( buf + ( HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE ) ); // sizeof EVT_LE_META_EVENT_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 .. @@ -351,18 +323,18 @@ int HCIAdapter::discoverDevices(HCISession& session, 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\n", + 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 == EVT_LE_META_EVENT + // HCI_LE_Advertising_Report == 0x3E == HCI_EV_LE_META // 0x3E 0x02 - if ( HCI_Event_Types::LE_Advertising_Report != ehdr->evt || meta->subevent != EVT_LE_ADVERTISING_REPORT ) { + if ( HCI_Event_Types::LE_Advertising_Report != ehdr->evt || meta->subevent != HCI_EV_LE_ADVERTISING_REPORT ) { continue; // next .. } - bytes_left -= EVT_LE_META_EVENT_SIZE; + bytes_left -= sizeof(hci_ev_le_meta); - std::vector<std::shared_ptr<EInfoReport>> ad_reports = EInfoReport::read_ad_reports(meta->data, bytes_left); + 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++) { @@ -375,17 +347,17 @@ int HCIAdapter::discoverDevices(HCISession& session, done = true; } } - DBG_PRINT("HCIAdapter::discovery[%d] %d/%d: matches %d, waitForDevice %s, ad_req %s, matchCount %d/%d, done %d\n", + 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\n", loop-1, i, num_reports, ad_report->toString().c_str()); + 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)); - addScannedDevice(dev); + scannedDevices.push_back(dev); } else { // existing device dev = scannedDevices.at(idx); @@ -412,6 +384,8 @@ done: 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/tinyb_hci/HCIDevice.cpp b/src/direct_bt/HCIDevice.cpp index c674cf75..6abd854a 100644 --- a/src/tinyb_hci/HCIDevice.cpp +++ b/src/direct_bt/HCIDevice.cpp @@ -32,23 +32,12 @@ #include <algorithm> -extern "C" { - #include <bluetooth/bluetooth.h> - #include <bluetooth/hci.h> - #include <bluetooth/hci_lib.h> -} - +#include "HCIComm.hpp" #include "HCITypes.hpp" -using namespace tinyb_hci; +#include "dbt_debug.hpp" -static inline const bdaddr_t* const_eui48_to_const_bdaddr_ptr(const EUI48 *p) { - return static_cast<const bdaddr_t *>( static_cast<void *>( const_cast<EUI48 *>( p ) ) ); -} - -// ************************************************* -// ************************************************* -// ************************************************* +using namespace direct_bt; HCIDevice::HCIDevice(HCIAdapter const & a, EInfoReport const & r) : adapter(a), ts_creation(r.getTimestamp()), mac(r.getAddress()) @@ -59,24 +48,37 @@ HCIDevice::HCIDevice(HCIAdapter const & a, EInfoReport const & r) update(r); } -void HCIDevice::addService(std::shared_ptr<UUID> const &uuid) +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>> const & services) +void HCIDevice::addServices(std::vector<std::shared_ptr<uuid_t>> const & services) { - for(int j=0; j<services.size(); j++) { - const std::shared_ptr<UUID> uuid = services.at(j); + 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> const &uuid) const +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> const& p) { + auto it = std::find_if(begin, services.end(), [&](std::shared_ptr<uuid_t> const& p) { return *p == *uuid; }); if ( it == std::end(services) ) { @@ -94,9 +96,13 @@ std::string HCIDevice::toString() const { ", 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> p = *it; - out.append(" ").append(p->toUUID128String()).append(", ").append(std::to_string(static_cast<int>(p->type))).append(" bytes\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; @@ -126,34 +132,28 @@ void HCIDevice::update(EInfoReport const & data) { addServices(data.getServices()); } -uint16_t HCIDevice::le_connect(HCISession &s, +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, - uint8_t peer_mac_type, - uint8_t own_mac_type ) + uint8_t initiator_filter ) { - if( !s.isOpen() ) { + if( !session.isOpen() ) { fprintf(stderr, "Session not open\n"); return 0; } - uint16_t handle = 0; - bdaddr_t bdmac; - memcpy(&bdmac, const_eui48_to_const_bdaddr_ptr(&mac), sizeof(bdaddr_t)); - - int err = hci_le_create_conn(s.dd(), - cpu_to_le(interval), cpu_to_le(window), - initiator_filter, peer_mac_type, bdmac, own_mac_type, - cpu_to_le(min_interval), cpu_to_le(max_interval), - cpu_to_le(latency), cpu_to_le(supervision_timeout), - cpu_to_le(min_ce_length), cpu_to_le(max_ce_length), - &handle, to_connect_ms); - if (err < 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; } @@ -161,3 +161,66 @@ uint16_t HCIDevice::le_connect(HCISession &s, // ************************************************* // ************************************************* +/** + 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/tinyb_hci/UUID.cpp b/src/direct_bt/UUID.cpp index 88b28323..4acfb2e2 100644 --- a/src/tinyb_hci/UUID.cpp +++ b/src/direct_bt/UUID.cpp @@ -25,54 +25,95 @@ #include "UUID.hpp" -using namespace tinyb_hci; +#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 tinyb_hci::BT_BASE_UUID = UUID128( bt_base_uuid_be, 0, false ); +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::UUID128(UUID128 const & base_uuid, UUID16 const & uuid16, int const uuid16_le_octet_index) -: UUID(Type::UUID128), value(merge_uint128(base_uuid.value, uuid16.value, uuid16_le_octet_index)) {} +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::UUID128(UUID128 const & base_uuid, UUID32 const & uuid32, int const uuid32_le_octet_index) -: UUID(Type::UUID128), value(merge_uint128(base_uuid.value, uuid32.value, uuid32_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::toString() const { - char buffer[4+1]; - const int count = snprintf(buffer, sizeof(buffer), "%.4X", value); - if( 4 != count ) { - std::string msg("UUID16 string not of length 4 but "); - msg.append(std::to_string(count)); - throw InternalError(msg, E_FILE_LINE); +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 std::string(buffer); + return str; } -std::string UUID16::toUUID128String(UUID128 const & base_uuid, int const le_octet_index) const +std::string uuid16_t::toUUID128String(uuid128_t const & base_uuid, int const le_octet_index) const { - UUID128 u128(base_uuid, *this, le_octet_index); + uuid128_t u128(*this, base_uuid, le_octet_index); return u128.toString(); } -std::string UUID32::toString() const { - char buffer[8+1]; - int count = snprintf(buffer, sizeof(buffer), "%.8X", value); - if( 8 != count ) { - std::string msg("UUID32 string not of length 8 but "); - msg.append(std::to_string(count)); - throw InternalError(msg, E_FILE_LINE); +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 std::string(buffer); + return str; } -std::string UUID32::toUUID128String(UUID128 const & base_uuid, int const le_octet_index) const +std::string uuid32_t::toUUID128String(uuid128_t const & base_uuid, int const le_octet_index) const { - UUID128 u128(base_uuid, *this, le_octet_index); + uuid128_t u128(*this, base_uuid, le_octet_index); return u128.toString(); } -std::string UUID128::toString() const { +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 @@ -81,7 +122,10 @@ std::string UUID128::toString() const { // BE: low-mem - 87654321-0000-1000-8000-00805F9B34FB - high-mem // 0 1 2 3 4 5 // - char buffer[36+1]; + 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; @@ -104,18 +148,16 @@ std::string UUID128::toString() const { #else #error "Unexpected __BYTE_ORDER" #endif - int count = snprintf(buffer, sizeof(buffer), "%.8X-%.4X-%.4X-%.4X-%.8X%.4X", - part0, part1, part2, part3, part4, part5); - if( 36 != count ) { - std::string msg("UUID128 string not of length 36 but "); - msg.append(std::to_string(count)); - throw InternalError(msg, E_FILE_LINE); + 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 std::string(buffer); + return str; } -UUID128::UUID128(const std::string str) -: UUID(Type::UUID128) +uuid128_t::uuid128_t(const std::string str) +: uuid_t(TypeSize::UUID128_SZ) { uint32_t part0, part4; uint16_t part1, part2, part3, part5; @@ -154,3 +196,4 @@ UUID128::UUID128(const std::string str) #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/tinyb_hci/tinyb_hci.pc.cmake b/src/direct_bt/direct_bt.pc.cmake index 8a18a64e..873e34ad 100644 --- a/src/tinyb_hci/tinyb_hci.pc.cmake +++ b/src/direct_bt/direct_bt.pc.cmake @@ -1,9 +1,9 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib@LIB_SUFFIX@ -includedir=${prefix}/include/tinyb_hci +includedir=${prefix}/include/direct_bt -Name: tinyb_hci +Name: direct_bt Description: Tiny BLE HCI library Version: @tinyb_VERSION_STRING@ diff --git a/src/ieee11073/DataTypes.cpp b/src/ieee11073/DataTypes.cpp new file mode 100644 index 00000000..2c8c03fa --- /dev/null +++ b/src/ieee11073/DataTypes.cpp @@ -0,0 +1,107 @@ +/* + * 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 <cmath> + +#include <algorithm> + +#include "ieee11073/DataTypes.hpp" + +using namespace ieee11073; + +AbsoluteTime::AbsoluteTime(const uint8_t * data_le, const int size) { + if( nullptr == data_le || 0 == size ) { + return; + } + int i=0; + if( 2 > size ) return; + year = data_le[i+0] | data_le[i+1] << 8; + i+=2; + if( 3 > size ) return; + month = static_cast<int8_t>(data_le[i++]); + if( 4 > size ) return; + day = static_cast<int8_t>(data_le[i++]); + if( 5 > size ) return; + hour = static_cast<int8_t>(data_le[i++]); + if( 6 > size ) return; + minute = static_cast<int8_t>(data_le[i++]); + if( 7 > size ) return; + second = static_cast<int8_t>(data_le[i++]); + if( 8 > size ) return; + second_fractions = static_cast<int8_t>(data_le[i++]); +} + +std::string AbsoluteTime::toString() const { + char cbuf[24]; // '2020-04-04 10:58:59' 19 + second_fractions + snprintf(cbuf, sizeof(cbuf), "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", + (int)year, (int)month, (int)day, (int)hour, (int)minute, (int)second); + + std::string res(cbuf); + if( 0 != second_fractions ) { + res += "."+std::to_string(second_fractions); + } + return res; +} + +static const int32_t FIRST_RESERVED_VALUE = FloatTypes::ReservedFloatValues::MDER_POSITIVE_INFINITY; +static const uint32_t FIRST_S_RESERVED_VALUE = FloatTypes::ReservedSFloatValues::MDER_S_POSITIVE_INFINITY; + +static const float reserved_float_values[5] = {INFINITY, NAN, NAN, NAN, -INFINITY}; + +float FloatTypes::float16_IEEE11073_to_IEEE754(const uint16_t raw_bt_float16_le) { + uint16_t mantissa = raw_bt_float16_le & 0x0FFF; + int8_t exponent = raw_bt_float16_le >> 12; + + if( exponent >= 0x0008) { + exponent = - ( (0x000F + 1) - exponent ); + } + + if( mantissa >= FIRST_S_RESERVED_VALUE && + mantissa <= ReservedSFloatValues::MDER_S_NEGATIVE_INFINITY ) { + return reserved_float_values[mantissa - FIRST_S_RESERVED_VALUE]; + } + if ( mantissa >= 0x0800 ) { + mantissa = - ( ( 0x0FFF + 1 ) - mantissa ); + } + return mantissa * powf(10.0f, exponent); +} + +float FloatTypes::float32_IEEE11073_to_IEEE754(const uint32_t raw_bt_float32_le) { + int32_t mantissa = raw_bt_float32_le & 0xFFFFFF; + int8_t expoent = raw_bt_float32_le >> 24; + + if( mantissa >= FIRST_RESERVED_VALUE && + mantissa <= ReservedFloatValues::MDER_NEGATIVE_INFINITY ) { + return reserved_float_values[mantissa - FIRST_RESERVED_VALUE]; + } + if( mantissa >= 0x800000 ) { + mantissa = - ( ( 0xFFFFFF + 1 ) - mantissa ); + } + return mantissa * powf(10.0f, expoent); +} diff --git a/src/tinyb_hci/BasicTypes.cpp b/src/tinyb_hci/BasicTypes.cpp deleted file mode 100644 index 97ef1cfc..00000000 --- a/src/tinyb_hci/BasicTypes.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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" - -// #define _USE_BACKTRACE_ 1 - -#if _USE_BACKTRACE_ -extern "C" { - #include <execinfo.h> -} -#endif - -using namespace tinyb_hci; - -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 tinyb_hci::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 tinyb_hci::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 tinyb_hci::merge_uint128(uint128_t const & base_uuid, uint16_t const uuid16, 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 tinyb_hci::merge_uint128(uint128_t const & base_uuid, uint32_t const uuid32, 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; -} - diff --git a/src/tinyb_hci/CMakeLists.txt b/src/tinyb_hci/CMakeLists.txt deleted file mode 100644 index d50d40c6..00000000 --- a/src/tinyb_hci/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -set (tinyb_hci_LIB_INCLUDE_DIRS - ${PROJECT_SOURCE_DIR}/api - ${PROJECT_SOURCE_DIR}/api/tinyb_hci - ${PROJECT_SOURCE_DIR}/include -) - -include_directories( - ${tinyb_hci_LIB_INCLUDE_DIRS} - ${CMAKE_CURRENT_BINARY_DIR} -) - -set (tinyb_hci_LIB_SRCS - ${PROJECT_SOURCE_DIR}/src/tinyb_hci/BasicTypes.cpp - ${PROJECT_SOURCE_DIR}/src/tinyb_hci/UUID.cpp - ${PROJECT_SOURCE_DIR}/src/tinyb_hci/BTDataTypes.cpp - ${PROJECT_SOURCE_DIR}/src/tinyb_hci/HCIAdapter.cpp - ${PROJECT_SOURCE_DIR}/src/tinyb_hci/HCIDevice.cpp -# autogenerated files - ${CMAKE_CURRENT_BINARY_DIR}/../version.c -) - -add_library (tinyb_hci SHARED ${tinyb_hci_LIB_SRCS}) -target_link_libraries ( - tinyb_hci - ${CMAKE_THREAD_LIBS_INIT} - bluetooth -) - -set_target_properties( - tinyb_hci - PROPERTIES - SOVERSION ${tinyb_VERSION_MAJOR} - VERSION ${tinyb_VERSION_STRING} - CXX_STANDARD 11 -) -install (DIRECTORY ${PROJECT_SOURCE_DIR}/api/tinyb_hci/ DESTINATION include/tinyb_hci) - -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 (tinyb_hci.pc lib${LIB_SUFFIX}/pkgconfig) - -install(TARGETS tinyb_hci LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) - diff --git a/test/direct_bt/CMakeLists.txt b/test/direct_bt/CMakeLists.txt new file mode 100644 index 00000000..0aad6245 --- /dev/null +++ b/test/direct_bt/CMakeLists.txt @@ -0,0 +1,27 @@ +include_directories( + ${PROJECT_SOURCE_DIR}/include/cppunit + ${PROJECT_SOURCE_DIR}/api +) + +add_executable (test_uuid test_uuid.cpp) +add_executable (test_basictypes01 test_basictypes01.cpp) +add_executable (test_attpdu01 test_attpdu01.cpp) +add_executable (test_lfringbuffer01 test_lfringbuffer01.cpp) +add_executable (test_lfringbuffer11 test_lfringbuffer11.cpp) + +set_target_properties(list_mfg + PROPERTIES + CXX_STANDARD 11) + +target_link_libraries (test_basictypes01 direct_bt) +target_link_libraries (test_uuid direct_bt) +target_link_libraries (test_attpdu01 direct_bt) +target_link_libraries (test_lfringbuffer01 direct_bt) +target_link_libraries (test_lfringbuffer11 direct_bt) + +add_test (NAME basictypes01 COMMAND test_basictypes01) +add_test (NAME uuid COMMAND test_uuid) +add_test (NAME attpdu01 COMMAND test_attpdu01) +add_test (NAME lfringbuffer01 COMMAND test_lfringbuffer01) +add_test (NAME lfringbuffer11 COMMAND test_lfringbuffer11) + diff --git a/test/direct_bt/README.txt b/test/direct_bt/README.txt new file mode 100644 index 00000000..d49b9045 --- /dev/null +++ b/test/direct_bt/README.txt @@ -0,0 +1,11 @@ +Rudimentary unit testing for our direct_bt +using the very flat cppunit <https://github.com/cppunit/cppunit.git>. + +The 'cppunit.h' file has been dropped to: ${PROJECT_SOURCE_DIR}/include/cppunit/cppunit.h + +After a normal build, you may invoke testing via 'ctest' + +To see the normal test stdout/stderr, invoke 'ctest -V'. +Sadly I haven't seen a way to inject this into the CMakeLists.txt file. + + diff --git a/test/direct_bt/test_attpdu01.cpp b/test/direct_bt/test_attpdu01.cpp new file mode 100644 index 00000000..1e6c30ce --- /dev/null +++ b/test/direct_bt/test_attpdu01.cpp @@ -0,0 +1,38 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> + +#include <cppunit.h> + +#include <direct_bt/UUID.hpp> +// #include <direct_bt/BTAddress.hpp> +// #include <direct_bt/HCITypes.hpp> +#include <direct_bt/ATTPDUTypes.hpp> +// #include <direct_bt/GATTHandler.hpp> +// #include <direct_bt/GATTIoctl.hpp> + +using namespace direct_bt; + +// Test examples. +class Cppunit_tests: public Cppunit { + void single_test() override { + const uuid16_t uuid16 = uuid16_t(uuid16_t(0x1234)); + const AttReadByNTypeReq req(true /* group */, 1, 0xffff, uuid16); + + std::shared_ptr<const uuid_t> uuid16_2 = req.getNType(); + CHECK(uuid16.getTypeSize(), 2); + CHECK(uuid16_2->getTypeSize(), 2); + CHECKT( 0 == memcmp(uuid16.data(), uuid16_2->data(), 2) ) + CHECKT( uuid16.toString() == uuid16_2->toString() ); + + CHECK(req.getStartHandle(), 1); + CHECK(req.getEndHandle(), 0xffff); + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + diff --git a/test/direct_bt/test_basictypes01.cpp b/test/direct_bt/test_basictypes01.cpp new file mode 100644 index 00000000..e541c4ac --- /dev/null +++ b/test/direct_bt/test_basictypes01.cpp @@ -0,0 +1,68 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> + +#include <cppunit.h> + +#include <direct_bt/BasicTypes.hpp> + +using namespace direct_bt; + +// Test examples. +class Cppunit_tests : public Cppunit { + private: + + void test_int32_t(const std::string msg, const int32_t v, const int expStrLen, const std::string expStr) { + const std::string str = int32SeparatedString(v); + PRINTM(msg+": has '"+str+"', len "+std::to_string(str.length())); + PRINTM(msg+": exp '"+expStr+"', len "+std::to_string(expStr.length())+", equal: "+std::to_string(str==expStr)); + CHECKM(msg, str.length(), expStrLen); + CHECKTM(msg, str == expStr); + } + + void test_uint32_t(const std::string msg, const uint32_t v, const int expStrLen, const std::string expStr) { + const std::string str = uint32SeparatedString(v); + PRINTM(msg+": has '"+str+"', len "+std::to_string(str.length())); + PRINTM(msg+": exp '"+expStr+"', len "+std::to_string(expStr.length())+", equal: "+std::to_string(str==expStr)); + CHECKM(msg, str.length(), expStrLen); + CHECKTM(msg, str == expStr); + } + + void test_uint64_t(const std::string msg, const uint64_t v, const int expStrLen, const std::string expStr) { + const std::string str = uint64SeparatedString(v); + PRINTM(msg+": has '"+str+"', len "+std::to_string(str.length())); + PRINTM(msg+": exp '"+expStr+"', len "+std::to_string(expStr.length())+", equal: "+std::to_string(str==expStr)); + CHECKM(msg, str.length(), expStrLen); + CHECKTM(msg, str == expStr); + } + + public: + void single_test() override { + + { + test_int32_t("INT32_MIN", INT32_MIN, 14, "-2,147,483,648"); + test_int32_t("int32_t -thousand", -1000, 6, "-1,000"); + test_int32_t("int32_t one", 1, 1, "1"); + test_int32_t("int32_t thousand", 1000, 5, "1,000"); + test_int32_t("INT32_MAX", INT32_MAX, 13, "2,147,483,647"); + + test_uint32_t("UINT32_MIN", 0, 1, "0"); + test_uint32_t("uint32_t one", 1, 1, "1"); + test_uint32_t("uint32_t thousand", 1000, 5, "1,000"); + test_uint32_t("UINT32_MAX", UINT32_MAX, 13, "4,294,967,295"); + + test_uint64_t("UINT64_MIN", 0, 1, "0"); + test_uint64_t("uint64_t one", 1, 1, "1"); + test_uint64_t("uint64_t thousand", 1000, 5, "1,000"); + test_uint64_t("UINT64_MAX", UINT64_MAX, 26, "18,446,744,073,709,551,615"); + } + + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + diff --git a/test/direct_bt/test_lfringbuffer01.cpp b/test/direct_bt/test_lfringbuffer01.cpp new file mode 100644 index 00000000..97297513 --- /dev/null +++ b/test/direct_bt/test_lfringbuffer01.cpp @@ -0,0 +1,331 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> +#include <memory> + +#include <cppunit.h> + +#include <direct_bt/UUID.hpp> +#include <direct_bt/Ringbuffer.hpp> +#include <direct_bt/LFRingbuffer.hpp> + +using namespace direct_bt; + +class Integer { + public: + int value; + + Integer(int v) : value(v) {} + + Integer(const Integer &o) noexcept = default; + Integer(Integer &&o) noexcept = default; + Integer& operator=(const Integer &o) noexcept = default; + Integer& operator=(Integer &&o) noexcept = default; + + operator int() const { + return value; + } + int intValue() const { return value; } + static Integer valueOf(const int i) { return Integer(i); } +}; + +std::shared_ptr<Integer> NullInteger = nullptr; + +typedef std::shared_ptr<Integer> SharedType; +typedef Ringbuffer<SharedType> SharedTypeRingbuffer; +typedef LFRingbuffer<SharedType, nullptr> SharedTypeLFRingbuffer; + +// Test examples. +class Cppunit_tests : public Cppunit { + private: + + std::shared_ptr<SharedTypeRingbuffer> createEmpty(int initialCapacity) { + return std::shared_ptr<SharedTypeRingbuffer>(new SharedTypeLFRingbuffer(initialCapacity)); + } + std::shared_ptr<SharedTypeRingbuffer> createFull(const std::vector<std::shared_ptr<Integer>> & source) { + return std::shared_ptr<SharedTypeRingbuffer>(new SharedTypeLFRingbuffer(source)); + } + + std::vector<SharedType> createIntArray(const int capacity, const int startValue) { + std::vector<SharedType> array(capacity); + for(int i=0; i<capacity; i++) { + array[i] = SharedType(new Integer(startValue+i)); + } + return array; + } + + void readTestImpl(Ringbuffer<SharedType> &rb, bool clearRef, int capacity, int len, int startValue) { + int preSize = rb.getSize(); + CHECKM("Wrong capacity "+rb.toString(), capacity, rb.capacity()); + CHECKTM("Too low capacity to read "+std::to_string(len)+" elems: "+rb.toString(), capacity-len >= 0); + CHECKTM("Too low size to read "+std::to_string(len)+" elems: "+rb.toString(), preSize >= len); + CHECKTM("Is empty "+rb.toString(), !rb.isEmpty()); + + for(int i=0; i<len; i++) { + SharedType svI = rb.get(); + CHECKTM("Empty at read #"+std::to_string(i+1)+": "+rb.toString(), svI!=nullptr); + CHECKM("Wrong value at read #"+std::to_string(i+1)+": "+rb.toString(), startValue+i, svI->intValue()); + } + + CHECKM("Invalid size "+rb.toString(), preSize-len, rb.getSize()); + CHECKTM("Invalid free slots after reading "+std::to_string(len)+": "+rb.toString(), rb.getFreeSlots()>= len); + CHECKTM("Is full "+rb.toString(), !rb.isFull()); + } + + void writeTestImpl(Ringbuffer<SharedType> &rb, int capacity, int len, int startValue) { + int preSize = rb.getSize(); + + CHECKM("Wrong capacity "+rb.toString(), capacity, rb.capacity()); + CHECKTM("Too low capacity to write "+std::to_string(len)+" elems: "+rb.toString(), capacity-len >= 0); + CHECKTM("Too low size to write "+std::to_string(len)+" elems: "+rb.toString(), preSize+len <= capacity); + CHECKTM("Is full "+rb.toString(), !rb.isFull()); + + for(int i=0; i<len; i++) { + std::string m = "Buffer is full at put #"+std::to_string(i)+": "+rb.toString(); + CHECKTM(m, rb.put( SharedType( new Integer(startValue+i) ) ) ); + } + + CHECKM("Invalid size "+rb.toString(), preSize+len, rb.getSize()); + CHECKTM("Is empty "+rb.toString(), !rb.isEmpty()); + } + + void moveGetPutImpl(Ringbuffer<SharedType> &rb, int pos) { + CHECKTM("RB is empty "+rb.toString(), !rb.isEmpty()); + for(int i=0; i<pos; i++) { + CHECKM("MoveFull.get failed "+rb.toString(), i, rb.get()->intValue()); + CHECKTM("MoveFull.put failed "+rb.toString(), rb.put( SharedType( new Integer(i) ) ) ); + } + } + + void movePutGetImpl(Ringbuffer<SharedType> &rb, int pos) { + CHECKTM("RB is full "+rb.toString(), !rb.isFull()); + for(int i=0; i<pos; i++) { + CHECKTM("MoveEmpty.put failed "+rb.toString(), rb.put( SharedType( new Integer(600+i) ) ) ); + CHECKM("MoveEmpty.get failed "+rb.toString(), 600+i, rb.get()->intValue()); + } + } + + void test01_FullRead() { + int capacity = 11; + std::vector<SharedType> source = createIntArray(capacity, 0); + std::shared_ptr<SharedTypeRingbuffer> rb = createFull(source); + fprintf(stderr, "test01_FullRead: Created / %s\n", rb->toString().c_str()); + CHECKM("Not full size "+rb->toString(), capacity, rb->getSize()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, true, capacity, capacity, 0); + fprintf(stderr, "test01_FullRead: PostRead / %s\n", rb->toString().c_str()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test02_EmptyWrite() { + int capacity = 11; + std::shared_ptr<Ringbuffer<SharedType>> rb = createEmpty(capacity); + fprintf(stderr, "test01_EmptyWrite: Created / %s\n", rb->toString().c_str()); + CHECKM("Not zero size "+rb->toString(), 0, rb->getSize()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + writeTestImpl(*rb, capacity, capacity, 0); + fprintf(stderr, "test01_EmptyWrite: PostWrite / %s\n", rb->toString().c_str()); + CHECKM("Not full size "+rb->toString(), capacity, rb->getSize()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, true, capacity, capacity, 0); + fprintf(stderr, "test01_EmptyWrite: PostRead / %s\n", rb->toString().c_str()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test03_FullReadReset() { + int capacity = 11; + std::vector<SharedType> source = createIntArray(capacity, 0); + std::shared_ptr<Ringbuffer<SharedType>> rb = createFull(source); + fprintf(stderr, "test01_FullReadReset: Created / %s\n", rb->toString().c_str()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + rb->reset(source); + fprintf(stderr, "test01_FullReadReset: Post Reset w/ source / %s\n", rb->toString().c_str()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + fprintf(stderr, "test01_FullReadReset: Post Read / %s\n", rb->toString().c_str()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + rb->reset(source); + fprintf(stderr, "test01_FullReadReset: Post Reset w/ source / %s\n", rb->toString().c_str()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + fprintf(stderr, "test01_FullReadReset: Post Read / %s\n", rb->toString().c_str()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test04_EmptyWriteClear() { + int capacity = 11; + std::shared_ptr<Ringbuffer<SharedType>> rb = createEmpty(capacity); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + rb->clear(); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + writeTestImpl(*rb, capacity, capacity, 0); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + rb->clear(); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + writeTestImpl(*rb, capacity, capacity, 0); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test05_ReadResetMid01() { + int capacity = 11; + std::vector<SharedType> source = createIntArray(capacity, 0); + std::shared_ptr<Ringbuffer<SharedType>> rb = createFull(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + rb->reset(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, 5, 0); + CHECKTM("Is empty "+rb->toString(), !rb->isEmpty()); + CHECKTM("Is Full "+rb->toString(), !rb->isFull()); + + rb->reset(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test06_ReadResetMid02() { + int capacity = 11; + std::vector<SharedType> source = createIntArray(capacity, 0); + std::shared_ptr<Ringbuffer<SharedType>> rb = createFull(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + rb->reset(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + moveGetPutImpl(*rb, 5); + readTestImpl(*rb, false, capacity, 5, 5); + CHECKTM("Is empty "+rb->toString(), !rb->isEmpty()); + CHECKTM("Is Full "+rb->toString(), !rb->isFull()); + + rb->reset(source); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + readTestImpl(*rb, false, capacity, capacity, 0); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + } + + void test_GrowFullImpl(int initialCapacity, int pos) { + int growAmount = 5; + int grownCapacity = initialCapacity+growAmount; + std::vector<SharedType> source = createIntArray(initialCapacity, 0); + std::shared_ptr<Ringbuffer<SharedType>> rb = createFull(source); + + for(int i=0; i<initialCapacity; i++) { + SharedType svI = rb->get(); + CHECKTM("Empty at read #"+std::to_string(i+1)+": "+rb->toString(), svI!=nullptr); + CHECKM("Wrong value at read #"+std::to_string(i+1)+": "+rb->toString(), (0+i)%initialCapacity, svI->intValue()); + } + CHECKM("Not zero size "+rb->toString(), 0, rb->getSize()); + + rb->reset(source); + CHECKM("Not orig size "+rb->toString(), initialCapacity, rb->getSize()); + + moveGetPutImpl(*rb, pos); + // PRINTM("X02 "+rb->toString()); + // rb->dump(stderr, "X02"); + + rb->recapacity(grownCapacity); + CHECKM("Wrong capacity "+rb->toString(), grownCapacity, rb->capacity()); + CHECKM("Not orig size "+rb->toString(), initialCapacity, rb->getSize()); + CHECKTM("Is full "+rb->toString(), !rb->isFull()); + CHECKTM("Is empty "+rb->toString(), !rb->isEmpty()); + // PRINTM("X03 "+rb->toString()); + // rb->dump(stderr, "X03"); + + for(int i=0; i<growAmount; i++) { + CHECKTM("Buffer is full at put #"+std::to_string(i)+": "+rb->toString(), rb->put( SharedType( new Integer(100+i) ) ) ); + } + CHECKM("Not new size "+rb->toString(), grownCapacity, rb->getSize()); + CHECKTM("Not full "+rb->toString(), rb->isFull()); + + for(int i=0; i<initialCapacity; i++) { + SharedType svI = rb->get(); + // PRINTM("X05["+std::to_string(i)+"]: "+rb->toString()+", svI-null: "+std::to_string(svI==nullptr)); + CHECKTM("Empty at read #"+std::to_string(i+1)+": "+rb->toString(), svI!=nullptr); + CHECKM("Wrong value at read #"+std::to_string(i+1)+": "+rb->toString(), (pos+i)%initialCapacity, svI->intValue()); + } + + for(int i=0; i<growAmount; i++) { + SharedType svI = rb->get(); + // PRINTM("X07["+std::to_string(i)+"]: "+rb->toString()+", svI-null: "+std::to_string(svI==nullptr)); + CHECKTM("Empty at read #"+std::to_string(i+1)+": "+rb->toString(), svI!=nullptr); + CHECKM("Wrong value at read #"+std::to_string(i+1)+": "+rb->toString(), 100+i, svI->intValue()); + } + + CHECKM("Not zero size "+rb->toString(), 0, rb->getSize()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + CHECKTM("Is full "+rb->toString(), !rb->isFull()); + } + + public: + + void test20_GrowFull01_Begin() { + test_GrowFullImpl(11, 0); + } + void test21_GrowFull02_Begin1() { + test_GrowFullImpl(11, 0+1); + } + void test22_GrowFull03_Begin2() { + test_GrowFullImpl(11, 0+2); + } + void test23_GrowFull04_Begin3() { + test_GrowFullImpl(11, 0+3); + } + void test24_GrowFull05_End() { + test_GrowFullImpl(11, 11-1); + } + void test25_GrowFull11_End1() { + test_GrowFullImpl(11, 11-1-1); + } + void test26_GrowFull12_End2() { + test_GrowFullImpl(11, 11-1-2); + } + void test27_GrowFull13_End3() { + test_GrowFullImpl(11, 11-1-3); + } + + void test_list() override { + test01_FullRead(); + test02_EmptyWrite(); + test03_FullReadReset(); + test04_EmptyWriteClear(); + test05_ReadResetMid01(); + test06_ReadResetMid02(); + + test20_GrowFull01_Begin(); + test21_GrowFull02_Begin1(); + test22_GrowFull03_Begin2(); + test23_GrowFull04_Begin3(); + test24_GrowFull05_End(); + test25_GrowFull11_End1(); + test26_GrowFull12_End2(); + test27_GrowFull13_End3(); + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + diff --git a/test/direct_bt/test_lfringbuffer11.cpp b/test/direct_bt/test_lfringbuffer11.cpp new file mode 100644 index 00000000..43350395 --- /dev/null +++ b/test/direct_bt/test_lfringbuffer11.cpp @@ -0,0 +1,178 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> +#include <memory> +#include <thread> +#include <pthread.h> + +#include <cppunit.h> + +#include <direct_bt/UUID.hpp> +#include <direct_bt/Ringbuffer.hpp> +#include <direct_bt/LFRingbuffer.hpp> + +using namespace direct_bt; + +class Integer { + public: + int value; + + Integer(int v) : value(v) {} + + Integer(const Integer &o) noexcept = default; + Integer(Integer &&o) noexcept = default; + Integer& operator=(const Integer &o) noexcept = default; + Integer& operator=(Integer &&o) noexcept = default; + + operator int() const { + return value; + } + int intValue() const { return value; } + static Integer valueOf(const int i) { return Integer(i); } +}; + +std::shared_ptr<Integer> NullInteger = nullptr; + +typedef std::shared_ptr<Integer> SharedType; +typedef Ringbuffer<SharedType> SharedTypeRingbuffer; +typedef LFRingbuffer<SharedType, nullptr> SharedTypeLFRingbuffer; + +// Test examples. +class Cppunit_tests : public Cppunit { + private: + + std::shared_ptr<SharedTypeRingbuffer> createEmpty(int initialCapacity) { + return std::shared_ptr<SharedTypeRingbuffer>(new SharedTypeLFRingbuffer(initialCapacity)); + } + std::shared_ptr<SharedTypeRingbuffer> createFull(const std::vector<std::shared_ptr<Integer>> & source) { + return std::shared_ptr<SharedTypeRingbuffer>(new SharedTypeLFRingbuffer(source)); + } + + std::vector<SharedType> createIntArray(const int capacity, const int startValue) { + std::vector<SharedType> array(capacity); + for(int i=0; i<capacity; i++) { + array[i] = SharedType(new Integer(startValue+i)); + } + return array; + } + + void getThreadType01(const std::string msg, std::shared_ptr<Ringbuffer<SharedType>> rb, int len, int startValue) { + // std::thread::id this_id = std::this_thread::get_id(); + // pthread_t this_id = pthread_self(); + + fprintf(stderr, "%s: Created / %s\n", msg.c_str(), rb->toString().c_str()); + for(int i=0; i<len; i++) { + SharedType svI = rb->getBlocking(); + CHECKTM(msg+": Empty at read #"+std::to_string(i+1)+": "+rb->toString(), svI!=nullptr); + fprintf(stderr, "%s: Got %d / %s\n", + msg.c_str(), svI->intValue(), rb->toString().c_str()); + if( 0 <= startValue ) { + CHECKM(msg+": %s: Wrong value at read #"+std::to_string(i+1)+": "+rb->toString(), startValue+i, svI->intValue()); + } + } + fprintf(stderr, "%s: Dies / %s\n", msg.c_str(), rb->toString().c_str()); + } + + void putThreadType01(const std::string msg, std::shared_ptr<Ringbuffer<SharedType>> rb, int len, int startValue) { + // std::thread::id this_id = std::this_thread::get_id(); + // pthread_t this_id = pthread_self(); + + fprintf(stderr, "%s: Created / %s\n", msg.c_str(), rb->toString().c_str()); + int preSize = rb->getSize(); + + for(int i=0; i<len; i++) { + Integer * vI = new Integer(startValue+i); + fprintf(stderr, "%s: Putting %d ... / %s\n", + msg.c_str(), vI->intValue(), rb->toString().c_str()); + rb->putBlocking( SharedType( vI ) ); + } + fprintf(stderr, "%s: Dies / %s\n", msg.c_str(), rb->toString().c_str()); + } + + public: + + void test01_Read1Write1() { + fprintf(stderr, "\n\ntest01_Read1Write1\n"); + int capacity = 100; + std::shared_ptr<SharedTypeRingbuffer> rb = createEmpty(capacity); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + std::thread getThread01(&Cppunit_tests::getThreadType01, this, "test01.get01", rb, capacity, 0); + std::thread putThread01(&Cppunit_tests::putThreadType01, this, "test01.put01", rb, capacity, 0); + putThread01.join(); + getThread01.join(); + + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + } + + void test02_Read4Write1() { + fprintf(stderr, "\n\ntest02_Read4Write1\n"); + int capacity = 400; + std::shared_ptr<SharedTypeRingbuffer> rb = createEmpty(capacity); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + std::thread getThread01(&Cppunit_tests::getThreadType01, this, "test02.get01", rb, capacity/4, -1); + std::thread getThread02(&Cppunit_tests::getThreadType01, this, "test02.get02", rb, capacity/4, -1); + std::thread putThread01(&Cppunit_tests::putThreadType01, this, "test02.put01", rb, capacity, 0); + std::thread getThread03(&Cppunit_tests::getThreadType01, this, "test02.get03", rb, capacity/4, -1); + std::thread getThread04(&Cppunit_tests::getThreadType01, this, "test02.get04", rb, capacity/4, -1); + putThread01.join(); + getThread01.join(); + getThread02.join(); + getThread03.join(); + getThread04.join(); + + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + } + + void test03_Read8Write2() { + fprintf(stderr, "\n\ntest03_Read8Write2\n"); + int capacity = 800; + std::shared_ptr<SharedTypeRingbuffer> rb = createEmpty(capacity); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + + std::thread getThread01(&Cppunit_tests::getThreadType01, this, "test03.get01", rb, capacity/8, -1); + std::thread getThread02(&Cppunit_tests::getThreadType01, this, "test03.get02", rb, capacity/8, -1); + std::thread putThread01(&Cppunit_tests::putThreadType01, this, "test03.put01", rb, capacity/2, 0); + std::thread getThread03(&Cppunit_tests::getThreadType01, this, "test03.get03", rb, capacity/8, -1); + std::thread getThread04(&Cppunit_tests::getThreadType01, this, "test03.get04", rb, capacity/8, -1); + + std::thread getThread05(&Cppunit_tests::getThreadType01, this, "test03.get05", rb, capacity/8, -1); + std::thread getThread06(&Cppunit_tests::getThreadType01, this, "test03.get06", rb, capacity/8, -1); + std::thread putThread02(&Cppunit_tests::putThreadType01, this, "test03.put02", rb, capacity/2, 400); + std::thread getThread07(&Cppunit_tests::getThreadType01, this, "test03.get07", rb, capacity/8, -1); + std::thread getThread08(&Cppunit_tests::getThreadType01, this, "test03.get08", rb, capacity/8, -1); + + putThread01.join(); + putThread02.join(); + getThread01.join(); + getThread02.join(); + getThread03.join(); + getThread04.join(); + getThread05.join(); + getThread06.join(); + getThread07.join(); + getThread08.join(); + + CHECKTM("Not empty "+rb->toString(), rb->isEmpty()); + CHECKM("Not empty size "+rb->toString(), 0, rb->getSize()); + } + + void test_list() override { + test01_Read1Write1(); + test02_Read4Write1(); + test03_Read8Write2(); + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + diff --git a/test/direct_bt/test_uuid.cpp b/test/direct_bt/test_uuid.cpp new file mode 100644 index 00000000..44638373 --- /dev/null +++ b/test/direct_bt/test_uuid.cpp @@ -0,0 +1,70 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> + +#include <cppunit.h> + +#include <direct_bt/UUID.hpp> + +using namespace direct_bt; + +// Test examples. +class Cppunit_tests : public Cppunit { + public: + void single_test() override { + + std::cout << "Hello COUT" << std::endl; + std::cerr << "Hello CERR" << std::endl; + + uint8_t buffer[100]; + static uint8_t uuid128_bytes[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; + + { + const uuid128_t v01 = uuid128_t(uuid128_bytes, 0, true); + CHECK(v01.getTypeSize(), 16); + CHECK(v01.getTypeSize(), sizeof(v01.value)); + CHECK(v01.getTypeSize(), sizeof(v01.value.data)); + CHECKT( 0 == memcmp(uuid128_bytes, v01.data(), 16) ) + + put_uuid(buffer, 0, v01, true); + std::shared_ptr<const uuid_t> v02 = uuid_t::create(uuid_t::TypeSize::UUID128_SZ, buffer, 0, true); + CHECK(v02->getTypeSize(), 16); + CHECKT( 0 == memcmp(v01.data(), v02->data(), 16) ) + CHECKT( v01.toString() == v02->toString() ); + } + + { + const uuid32_t v01 = uuid32_t(uuid32_t(0x12345678)); + CHECK(v01.getTypeSize(), 4); + CHECK(v01.getTypeSize(), sizeof(v01.value)); + CHECK(0x12345678, v01.value); + + put_uuid(buffer, 0, v01, true); + std::shared_ptr<const uuid_t> v02 = uuid_t::create(uuid_t::TypeSize::UUID32_SZ, buffer, 0, true); + CHECK(v02->getTypeSize(), 4); + CHECKT( 0 == memcmp(v01.data(), v02->data(), 4) ) + CHECKT( v01.toString() == v02->toString() ); + } + + { + const uuid16_t v01 = uuid16_t(uuid16_t(0x1234)); + CHECK(v01.getTypeSize(), 2); + CHECK(v01.getTypeSize(), sizeof(v01.value)); + CHECK(0x1234, v01.value); + + put_uuid(buffer, 0, v01, true); + std::shared_ptr<const uuid_t> v02 = uuid_t::create(uuid_t::TypeSize::UUID16_SZ, buffer, 0, true); + CHECK(v02->getTypeSize(), 2); + CHECKT( 0 == memcmp(v01.data(), v02->data(), 2) ) + CHECKT( v01.toString() == v02->toString() ); + } + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + diff --git a/test/ieee11073/CMakeLists.txt b/test/ieee11073/CMakeLists.txt new file mode 100644 index 00000000..34cdbc41 --- /dev/null +++ b/test/ieee11073/CMakeLists.txt @@ -0,0 +1,15 @@ +include_directories( + ${PROJECT_SOURCE_DIR}/include/cppunit + ${PROJECT_SOURCE_DIR}/api +) + +add_executable (test_datatypes01 test_datatypes01.cpp) + +set_target_properties(list_mfg + PROPERTIES + CXX_STANDARD 11) + +target_link_libraries (test_datatypes01 direct_bt) + +add_test (NAME datatypes01 COMMAND test_datatypes01) + diff --git a/test/ieee11073/test_datatypes01.cpp b/test/ieee11073/test_datatypes01.cpp new file mode 100644 index 00000000..12e0bd3d --- /dev/null +++ b/test/ieee11073/test_datatypes01.cpp @@ -0,0 +1,63 @@ +#include <iostream> +#include <cassert> +#include <cinttypes> +#include <cstring> + +#include <cppunit.h> + +#include "ieee11073/DataTypes.hpp" + +using namespace ieee11073; + +// Test examples. +class Cppunit_tests : public Cppunit { + private: + void test_float32_IEEE11073_to_IEEE754(const std::string msg, const uint32_t raw, const float expFloat) { + const float has = FloatTypes::float32_IEEE11073_to_IEEE754(raw); + PRINTM(msg+": has '"+std::to_string(has)); + PRINTM(msg+": exp '"+std::to_string(expFloat)+"', diff "+std::to_string(fabsf(has-expFloat))); + CHECKD(msg, has, expFloat); + } + + void test_AbsoluteTime_IEEE11073(const std::string msg, const uint8_t * data_le, const int size, const std::string expStr) { + ieee11073::AbsoluteTime has(data_le, size); + const std::string has_str = has.toString(); + PRINTM(msg+": has '"+has_str+"', len "+std::to_string(has_str.length())); + PRINTM(msg+": exp '"+expStr+"', len "+std::to_string(expStr.length())+", equal: "+std::to_string(has_str==expStr)); + CHECKM(msg, has_str.length(), expStr.length()); + CHECKTM(msg, has_str == expStr); + } + + public: + void single_test() override { + + { + // 0x06 670100FF E40704040B1A00 00 + // 0x06 640100FF E40704040B2C00 00 + + // 79 09 00 FE -> 24.25f + test_float32_IEEE11073_to_IEEE754("IEEE11073-float01", 0xFE000979, 24.25f); + // 670100FF -> 35.900002 + test_float32_IEEE11073_to_IEEE754("IEEE11073-float01", 0xFF000167, 35.900002f); + // 640100FF -> 35.600002 + test_float32_IEEE11073_to_IEEE754("IEEE11073-float02", 0xFF000164, 35.600002f); + + { + // E40704040B1A00 -> 2020-04-04 11:26:00 + const uint8_t input[] = { 0xE4, 0x07, 0x04, 0x04, 0x0B, 0x1A, 0x00 }; + test_AbsoluteTime_IEEE11073("IEEE11073 time01", input, 7, "2020-04-04 11:26:00"); + } + { + // E40704040B2C00 -> 2020-04-04 11:44:00 + const uint8_t input[] = { 0xE4, 0x07, 0x04, 0x04, 0x0B, 0x2C, 0x00 }; + test_AbsoluteTime_IEEE11073("IEEE11073 time02", input, 7, "2020-04-04 11:44:00"); + } + } + } +}; + +int main(int argc, char *argv[]) { + Cppunit_tests test1; + return test1.run(); +} + |