From 6feca1dc60a7e3277c504e50b566f08760c69baa Mon Sep 17 00:00:00 2001 From: Paul Nettle Date: Wed, 27 Sep 2017 19:37:25 -0500 Subject: Added a threaded worker that listens for HCI events, resolving a number of problem areas. --- src/GattCharacteristic.h | 8 + src/GattUuid.h | 2 +- src/HciAdapter.cpp | 671 ++++++++++++++++++++++++++++------------------- src/HciAdapter.h | 368 ++++++++++++++++++++++++-- src/HciSocket.cpp | 70 +++-- src/HciSocket.h | 3 - src/Init.cpp | 147 +++++------ src/Mgmt.cpp | 172 ++---------- src/Mgmt.h | 180 ++++++++----- src/TickEvent.h | 2 +- 10 files changed, 995 insertions(+), 628 deletions(-) diff --git a/src/GattCharacteristic.h b/src/GattCharacteristic.h index 3815b2a..7284631 100644 --- a/src/GattCharacteristic.h +++ b/src/GattCharacteristic.h @@ -40,6 +40,7 @@ #include "Utils.h" #include "TickEvent.h" #include "GattInterface.h" +#include "HciAdapter.h" namespace ggk { @@ -207,9 +208,16 @@ struct GattCharacteristic : GattInterface // // This is a helper method that accepts common types. For custom types, there is a form that accepts a `GVariant *`, called // `sendChangeNotificationVariant()`. + // + // If there are no connections, this method returns doing nothing. template void sendChangeNotificationValue(GDBusConnection *pBusConnection, T value) const { + if (HciAdapter::getInstance().getActiveConnectionCount() == 0) + { + return; + } + GVariant *pVariant = Utils::gvariantFromByteArray(value); sendChangeNotificationVariant(pBusConnection, pVariant); } diff --git a/src/GattUuid.h b/src/GattUuid.h index 9d95754..6cfb9ca 100644 --- a/src/GattUuid.h +++ b/src/GattUuid.h @@ -159,7 +159,7 @@ struct GattUuid { bitCount = 32; char partStr[9]; - snprintf(partStr, sizeof(partStr), "%04x", part); + snprintf(partStr, sizeof(partStr), "%08x", part); uuid = std::string(partStr) + kGattStandardUuidSuffix; } diff --git a/src/HciAdapter.cpp b/src/HciAdapter.cpp index 9000709..06c4457 100644 --- a/src/HciAdapter.cpp +++ b/src/HciAdapter.cpp @@ -57,130 +57,391 @@ #include "HciAdapter.h" #include "HciSocket.h" #include "Utils.h" +#include "Mgmt.h" #include "Logger.h" namespace ggk { -static const int kMinCommandCode = 0x0001; -static const int kMaxCommandCode = 0x0043; -static const char *kCommandCodeNames[kMaxCommandCode] = +// Our event thread listens for events coming from the adapter and deals with them appropriately +std::thread HciAdapter::eventThread; + +const char * const HciAdapter::kCommandCodeNames[kMaxCommandCode + 1] = { - "Read Management Version Information Command", // 0x0001 - "Read Management Supported Commands Command", // 0x0002 - "Read Controller Index List Command", // 0x0003 - "Read Controller Information Command", // 0x0004 - "Set Powered Command", // 0x0005 - "Set Discoverable Command", // 0x0006 - "Set Connectable Command", // 0x0007 - "Set Fast Connectable Command", // 0x0008 - "Set Bondable Command", // 0x0009 - "Set Link Security Command", // 0x000A - "Set Secure Simple Pairing Command", // 0x000B - "Set High Speed Command", // 0x000C - "Set Low Energy Command", // 0x000D - "Set Device Class", // 0x000E - "Set Local Name Command", // 0x000F - "Add UUID Command", // 0x0010 - "Remove UUID Command", // 0x0011 - "Load Link Keys Command", // 0x0012 - "Load Long Term Keys Command", // 0x0013 - "Disconnect Command", // 0x0014 - "Get Connections Command", // 0x0015 - "PIN Code Reply Command", // 0x0016 - "PIN Code Negative Reply Command", // 0x0017 - "Set IO Capability Command", // 0x0018 - "Pair Device Command", // 0x0019 - "Cancel Pair Device Command", // 0x001A - "Unpair Device Command", // 0x001B - "User Confirmation Reply Command", // 0x001C - "User Confirmation Negative Reply Command", // 0x001D - "User Passkey Reply Command", // 0x001E - "User Passkey Negative Reply Command", // 0x001F - "Read Local Out Of Band Data Command", // 0x0020 - "Add Remote Out Of Band Data Command", // 0x0021 - "Remove Remote Out Of Band Data Command", // 0x0022 - "Start Discovery Command", // 0x0023 - "Stop Discovery Command", // 0x0024 - "Confirm Name Command", // 0x0025 - "Block Device Command", // 0x0026 - "Unblock Device Command", // 0x0027 - "Set Device ID Command", // 0x0028 - "Set Advertising Command", // 0x0029 - "Set BR/EDR Command", // 0x002A - "Set Static Address Command", // 0x002B - "Set Scan Parameters Command", // 0x002C - "Set Secure Connections Command", // 0x002D - "Set Debug Keys Command", // 0x002E - "Set Privacy Command", // 0x002F - "Load Identity Resolving Keys Command", // 0x0030 - "Get Connection Information Command", // 0x0031 - "Get Clock Information Command", // 0x0032 - "Add Device Command", // 0x0033 - "Remove Device Command", // 0x0034 - "Load Connection Parameters Command", // 0x0035 - "Read Unconfigured Controller Index List Command", // 0x0036 + "Invalid Command", // 0x0000 + "Read Version Information Command", // 0x0001 + "Read Supported Commands Command", // 0x0002 + "Read Controller Index List Command", // 0x0003 + "Read Controller Information Command", // 0x0004 + "Set Powered Command", // 0x0005 + "Set Discoverable Command", // 0x0006 + "Set Connectable Command", // 0x0007 + "Set Fast Connectable Command", // 0x0008 + "Set Bondable Command", // 0x0009 + "Set Link Security Command", // 0x000A + "Set Secure Simple Pairing Command", // 0x000B + "Set High Speed Command", // 0x000C + "Set Low Energy Command", // 0x000D + "Set Device Class", // 0x000E + "Set Local Name Command", // 0x000F + "Add UUID Command", // 0x0010 + "Remove UUID Command", // 0x0011 + "Load Link Keys Command", // 0x0012 + "Load Long Term Keys Command", // 0x0013 + "Disconnect Command", // 0x0014 + "Get Connections Command", // 0x0015 + "PIN Code Reply Command", // 0x0016 + "PIN Code Negative Reply Command", // 0x0017 + "Set IO Capability Command", // 0x0018 + "Pair Device Command", // 0x0019 + "Cancel Pair Device Command", // 0x001A + "Unpair Device Command", // 0x001B + "User Confirmation Reply Command", // 0x001C + "User Confirmation Negative Reply Command", // 0x001D + "User Passkey Reply Command", // 0x001E + "User Passkey Negative Reply Command", // 0x001F + "Read Local Out Of Band Data Command", // 0x0020 + "Add Remote Out Of Band Data Command", // 0x0021 + "Remove Remote Out Of Band Data Command", // 0x0022 + "Start Discovery Command", // 0x0023 + "Stop Discovery Command", // 0x0024 + "Confirm Name Command", // 0x0025 + "Block Device Command", // 0x0026 + "Unblock Device Command", // 0x0027 + "Set Device ID Command", // 0x0028 + "Set Advertising Command", // 0x0029 + "Set BR/EDR Command", // 0x002A + "Set Static Address Command", // 0x002B + "Set Scan Parameters Command", // 0x002C + "Set Secure Connections Command", // 0x002D + "Set Debug Keys Command", // 0x002E + "Set Privacy Command", // 0x002F + "Load Identity Resolving Keys Command", // 0x0030 + "Get Connection Information Command", // 0x0031 + "Get Clock Information Command", // 0x0032 + "Add Device Command", // 0x0033 + "Remove Device Command", // 0x0034 + "Load Connection Parameters Command", // 0x0035 + "Read Unconfigured Controller Index List Command", // 0x0036 "Read Controller Configuration Information Command", // 0x0037 - "Set External Configuration Command", // 0x0038 - "Set Public Address Command", // 0x0039 - "Start Service Discovery Command", // 0x003a - "Read Local Out Of Band Extended Data Command", // 0x003b - "Read Extended Controller Index List Command", // 0x003c - "Read Advertising Features Command", // 0x003d - "Add Advertising Command", // 0x003e - "Remove Advertising Command", // 0x003f - "Get Advertising Size Information Command", // 0x0040 - "Start Limited Discovery Command", // 0x0041 - "Read Extended Controller Information Command", // 0x0042 + "Set External Configuration Command", // 0x0038 + "Set Public Address Command", // 0x0039 + "Start Service Discovery Command", // 0x003a + "Read Local Out Of Band Extended Data Command", // 0x003b + "Read Extended Controller Index List Command", // 0x003c + "Read Advertising Features Command", // 0x003d + "Add Advertising Command", // 0x003e + "Remove Advertising Command", // 0x003f + "Get Advertising Size Information Command", // 0x0040 + "Start Limited Discovery Command", // 0x0041 + "Read Extended Controller Information Command", // 0x0042 // NOTE: The documentation at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt) states that the command // code for "Set Appearance Command" is 0x0042. It also says this about the previous command in the list ("Read Extended // Controller Information Command".) This is likely an error, so I'm following the order of the commands as they appear in the // documentation. This makes "Set Appearance Code" have a command code of 0x0043. - "Set Appearance Command", // 0x0043 + "Set Appearance Command" // 0x0043 +}; + +const char * const HciAdapter::kEventTypeNames[kMaxEventType + 1] = +{ + "Invalid Event", // 0x0000 + "Command Complete Event", // 0x0001 + "Command Status Event", // 0x0002 + "Controller Error Event", // 0x0003 + "Index Added Event", // 0x0004 + "Index Removed Event", // 0x0005 + "New Settings Event", // 0x0006 + "Class Of Device Changed Event", // 0x0007 + "Local Name Changed Event", // 0x0008 + "New Link Key Event", // 0x0009 + "New Long Term Key Event", // 0x000A + "Device Connected Event", // 0x000B + "Device Disconnected Event", // 0x000C + "Connect Failed Event", // 0x000D + "PIN Code Request Event", // 0x000E + "User Confirmation Request Event", // 0x000F + "User Passkey Request Event", // 0x0010 + "Authentication Failed Event", // 0x0011 + "Device Found Event", // 0x0012 + "Discovering Event", // 0x0013 + "Device Blocked Event", // 0x0014 + "Device Unblocked Event", // 0x0015 + "Device Unpaired Event", // 0x0016 + "Passkey Notify Event", // 0x0017 + "New Identity Resolving Key Event", // 0x0018 + "New Signature Resolving Key Event", // 0x0019 + "Device Added Event", // 0x001a + "Device Removed Event", // 0x001b + "New Connection Parameter Event", // 0x001c + "Unconfigured Index Added Event", // 0x001d + "Unconfigured Index Removed Event", // 0x001e + "New Configuration Options Event", // 0x001f + "Extended Index Added Event", // 0x0020 + "Extended Index Removed Event", // 0x0021 + "Local Out Of Band Extended Data Updated Event", // 0x0022 + "Advertising Added Event", // 0x0023 + "Advertising Removed Event", // 0x0024 + "Extended Controller Information Changed Event" // 0x0025 }; -static const int kMinEventType = 0x0001; -static const int kMaxEventType = 0x0025; -static const char *kEventTypeNames[kMaxEventType] = +const char * const HciAdapter::kStatusCodes[kMaxStatusCode + 1] = { - "Command Complete Event", // 0x0001 - "Command Status Event", // 0x0002 - "Controller Error Event", // 0x0003 - "Index Added Event", // 0x0004 - "Index Removed Event", // 0x0005 - "New Settings Event", // 0x0006 - "Class Of Device Changed Event", // 0x0007 - "Local Name Changed Event", // 0x0008 - "New Link Key Event", // 0x0009 - "New Long Term Key Event", // 0x000A - "Device Connected Event", // 0x000B - "Device Disconnected Event", // 0x000C - "Connect Failed Event", // 0x000D - "PIN Code Request Event", // 0x000E - "User Confirmation Request Event", // 0x000F - "User Passkey Request Event", // 0x0010 - "Authentication Failed Event", // 0x0011 - "Device Found Event", // 0x0012 - "Discovering Event", // 0x0013 - "Device Blocked Event", // 0x0014 - "Device Unblocked Event", // 0x0015 - "Device Unpaired Event", // 0x0016 - "Passkey Notify Event", // 0x0017 - "New Identity Resolving Key Event", // 0x0018 - "New Signature Resolving Key Event", // 0x0019 - "Device Added Event", // 0x001a - "Device Removed Event", // 0x001b - "New Connection Parameter Event", // 0x001c - "Unconfigured Index Added Event", // 0x001d - "Unconfigured Index Removed Event", // 0x001e - "New Configuration Options Event", // 0x001f - "Extended Index Added Event", // 0x0020 - "Extended Index Removed Event", // 0x0021 - "Local Out Of Band Extended Data Updated Event", // 0x0022 - "Advertising Added Event", // 0x0023 - "Advertising Removed Event", // 0x0024 - "Extended Controller Information Changed Event" // 0x0025 + "Success", // 0x00 + "Unknown Command", // 0x01 + "Not Connected", // 0x02 + "Failed", // 0x03 + "Connect Failed", // 0x04 + "Authentication Failed", // 0x05 + "Not Paired", // 0x06 + "No Resources", // 0x07 + "Timeout", // 0x08 + "Already Connected", // 0x09 + "Busy", // 0x0A + "Rejected", // 0x0B + "Not Supported", // 0x0C + "Invalid Parameters", // 0x0D + "Disconnected", // 0x0E + "Not Powered", // 0x0F + "Cancelled", // 0x10 + "Invalid Index", // 0x11 + "RFKilled", // 0x12 + "Already Paired", // 0x13 + "Permission Denied", // 0x14 }; +HciAdapter::AdapterSettings HciAdapter::getAdapterSettings() +{ + return adapterSettings; +} + +HciAdapter::ControllerInformation HciAdapter::getControllerInformation() +{ + return controllerInformation; +} + +HciAdapter::VersionInformation HciAdapter::getVersionInformation() +{ + return versionInformation; +} + +HciAdapter::LocalName HciAdapter::getLocalName() +{ + return localName; +} + +// Our thread interface, which simply launches our the thread processor on our HciAdapter instance +void runEventThread() +{ + HciAdapter::getInstance().runEventThread(); +} + +// Event processor, responsible for receiving events from the HCI socket +// +// This mehtod should not be called directly. Rather, it runs continuously on a thread until the server shuts down +void HciAdapter::runEventThread() +{ + Logger::trace("Entering the HciAdapter event thread"); + + while (ggkGetServerRunState() <= ERunning) + { + // Read the next event, waiting until one arrives + std::vector responsePacket = std::vector(); + if (!hciSocket.read(responsePacket)) + { + break; + } + + // Do we have enough to check the event code? + if (responsePacket.size() < 2) + { + Logger::error(SSTR << "Invalid command response: too short"); + continue; + } + + // Our response, as a usable object type + uint16_t eventCode = Utils::endianToHost(*reinterpret_cast(responsePacket.data())); + + // Ensure our event code is valid + if (eventCode < HciAdapter::kMinEventType || eventCode > HciAdapter::kMaxEventType) + { + Logger::error(SSTR << "Invalid command response: event code (" << eventCode << ") out of range"); + continue; + } + + switch(eventCode) + { + // Command complete event + case Mgmt::ECommandCompleteEvent: + { + // Extract our event + CommandCompleteEvent event = *reinterpret_cast(responsePacket.data()); + + // Fixup endian + event.toHost(); + + // Log it + Logger::debug(event.debugText()); + + // Point to the data following the event + uint8_t *data = responsePacket.data() + sizeof(CommandCompleteEvent); + size_t dataLen = responsePacket.size() - sizeof(CommandCompleteEvent); + + switch(event.commandCode) + { + // We just log the version/revision info + case Mgmt::EReadVersionInformationCommand: + { + // Verify the size is what we expect + if (dataLen != sizeof(VersionInformation)) + { + Logger::error("Invalid data length"); + return; + } + + versionInformation = *reinterpret_cast(data); + versionInformation.toHost(); + Logger::debug(versionInformation.debugText()); + break; + } + case Mgmt::EReadControllerInformationCommand: + { + if (dataLen != sizeof(ControllerInformation)) + { + Logger::error("Invalid data length"); + return; + } + + controllerInformation = *reinterpret_cast(data); + controllerInformation.toHost(); + Logger::debug(controllerInformation.debugText()); + break; + } + case Mgmt::ESetLocalNameCommand: + { + if (dataLen != sizeof(LocalName)) + { + Logger::error("Invalid data length"); + return; + } + + localName = *reinterpret_cast(data); + Logger::info(localName.debugText()); + break; + } + case Mgmt::ESetPoweredCommand: + case Mgmt::ESetBREDRCommand: + case Mgmt::ESetSecureConnectionsCommand: + case Mgmt::ESetBondableCommand: + case Mgmt::ESetConnectableCommand: + case Mgmt::ESetLowEnergyCommand: + case Mgmt::ESetAdvertisingCommand: + { + if (dataLen != sizeof(AdapterSettings)) + { + Logger::error("Invalid data length"); + return; + } + + adapterSettings = *reinterpret_cast(data); + adapterSettings.toHost(); + + Logger::debug(adapterSettings.debugText()); + break; + } + } + break; + } + // Command status event + case Mgmt::ECommandStatusEvent: + { + // Extract our event + CommandStatusEvent event = *reinterpret_cast(responsePacket.data()); + + // Fixup endian + event.toHost(); + + // Log it + Logger::debug(event.debugText()); + + break; + } + // Command status event + case Mgmt::EDeviceConnectedEvent: + { + // Extract our event + DeviceConnectedEvent event = *reinterpret_cast(responsePacket.data()); + + // Fixup endian + event.toHost(); + + // Log it + Logger::debug(event.debugText()); + + // Track our connection count + activeConnections += 1; + + break; + } + // Command status event + case Mgmt::EDeviceDisconnectedEvent: + { + // Extract our event + DeviceDisconnectedEvent event = *reinterpret_cast(responsePacket.data()); + + // Fixup endian + event.toHost(); + + // Log it + Logger::debug(event.debugText()); + + // Track our connection count + activeConnections -= 1; + + break; + } + // Unsupported + default: + { + if (eventCode >= kMinEventType && eventCode <= kMaxEventType) + { + Logger::error("Unsupported response event type: " + Utils::hex(eventCode) + " (" + kEventTypeNames[eventCode] + ")"); + } + else + { + Logger::error("Invalid event type response: " + Utils::hex(eventCode)); + } + } + } + } + + Logger::trace("Leaving the HciAdapter event thread"); +} + +// Reads current values from the controller +// +// This effectively requests data from the controller but that data may not be available instantly, but within a few +// milliseconds. Therefore, it is not recommended attempt to retrieve the results from their accessors immediately. +void HciAdapter::sync(uint16_t controllerIndex) +{ + HciAdapter::HciHeader request; + request.code = Mgmt::EReadVersionInformationCommand; + request.controllerId = HciAdapter::kNonController; + request.dataSize = 0; + + if (!HciAdapter::getInstance().sendCommand(request)) + { + Logger::error("Failed to get version information"); + } + + request.code = Mgmt::EReadControllerInformationCommand; + request.controllerId = controllerIndex; + request.dataSize = 0; + + if (!HciAdapter::getInstance().sendCommand(request)) + { + Logger::error("Failed to get current settings"); + } +} + // Connects the HCI socket if a connection does not already exist // // If a connection already exists, this method will do nothing and return true. @@ -190,13 +451,36 @@ static const char *kEventTypeNames[kMaxEventType] = // Returns true if the HCI socket is connected (either via a new connection or an existing one), otherwise false bool HciAdapter::connect() { - // Connect if we aren't already connected - if (!isConnected() && !hciSocket.connect()) + // Already connected? + if (isConnected()) + { + return true; + } + + // Try to connect + if (!hciSocket.connect()) { disconnect(); return false; } + Logger::trace("Starting the HciAdapter thread"); + + // Create a thread to read the data from the socket + try + { + eventThread = std::thread(ggk::runEventThread); + } + catch(std::system_error &ex) + { + if (ex.code() == std::errc::resource_unavailable_try_again) + { + Logger::error(SSTR << "HciAdapter thread was unable to start: " << ex.what()); + disconnect(); + return false; + } + } + return true; } @@ -219,6 +503,14 @@ void HciAdapter::disconnect() { hciSocket.disconnect(); } + + if (eventThread.joinable()) + { + Logger::trace("Stopping the HciAdapter thread"); + + pthread_kill(eventThread.native_handle(), SIGINT); + eventThread.join(); + } } // Sends a command over the HCI socket @@ -227,15 +519,12 @@ void HciAdapter::disconnect() // a failure is returned. // // Returns true on success, otherwise false -bool HciAdapter::sendCommand(Header &request, ResponseEvent &response, int responseLen) +bool HciAdapter::sendCommand(HciHeader &request) { // Auto-connect if (!connect()) { return false; } - Logger::debug(" + Request header"); - Logger::debug(SSTR << " + Event code : " << Utils::hex(request.code)); - Logger::debug(SSTR << " + Controller Id : " << Utils::hex(request.controllerId)); - Logger::debug(SSTR << " + Data size : " << request.dataSize << " bytes"); + Logger::debug(request.debugText()); request.toNetwork(); uint8_t *pRequest = reinterpret_cast(&request); @@ -245,156 +534,8 @@ bool HciAdapter::sendCommand(Header &request, ResponseEvent &response, int respo return false; } - // Read the response for this particular command - return readResponse(request.code, response, responseLen); -} - -// Reads a response from the HCI socket -// -// Responses are generally triggered by sending commands (see `sendCommand`) but not always. In HCI parlance, a response is -// actually an event. Performing commands triggers events. There is not always a 1:1 ratio betwee command and event, and a -// command may trigger different events based on the result of the command. -// -// Unlike the other methods in this class, this method does not auto-connect, as this method is private and can only be called -// from methods that have alreay auto-connected. -bool HciAdapter::readResponse(uint16_t commandCode, ResponseEvent &response, size_t responseLen) const -{ - if (!hciSocket.isConnected()) { return false; } - - std::vector responsePacket = std::vector(); - if (!hciSocket.read(responsePacket)) - { - return false; - } - - // Validate our events and remove everything that isn't a command complete or status event - if (!filterAndValidateEvents(commandCode, responsePacket)) - { - return false; - } - - // Do we have enough to check the event code? - if (responsePacket.size() < 2) - { - Logger::error(SSTR << "Command's response was invalid"); - return false; - } - - // If this wasn't a Command Complete Event, just return an error - uint16_t eventCode = Utils::endianToHost(*reinterpret_cast(responsePacket.data())); - if (eventCode != 1) - { - return false; - } - - // It's a Command Complete event, verify the size is what we expect - if (responsePacket.size() != responseLen) - { - Logger::error(SSTR << "Command's response was " << responsePacket.size() << " bytes but was supposed to be " << responseLen << " bytes"); - return false; - } - - // Copy the response data into the requested structure - memcpy(&response, responsePacket.data(), responseLen); - - // Perform endian conversion - response.toHost(); - - // Log the header information - Logger::debug(" + Response event"); - Logger::debug(SSTR << " + Event code : " << Utils::hex(response.header.code)); - Logger::debug(SSTR << " + Controller Id : " << Utils::hex(response.header.controllerId)); - Logger::debug(SSTR << " + Data size : " << response.header.dataSize << " bytes"); - Logger::debug(SSTR << " + Command code : " << response.commandCode); - Logger::debug(SSTR << " + Status : " << Utils::hex(response.status)); - - // One last check... let's verify that our data size is what it should be - // - // Note that we add 3 - this is because there are two fields that always follow the - // data size (so they were included in our ResponseEvent structure) even - // though they are not counted as part of the size, so we account for that here. - int adjustedHeaderSize = sizeof(ResponseEvent) - 3; - int expectedDataSize = responseLen - adjustedHeaderSize; - if (response.header.dataSize != expectedDataSize) - { - Logger::error(SSTR << "The data size from the response (" << response.header.dataSize - << ") does not match the response structure (" << expectedDataSize - << ": " << responseLen << " - " << adjustedHeaderSize - << ") - this is likely a kernel version mismatch or bug in the code"); - } - - // Everything checks out - good to go! - return true; -} - -// Filter out events that we aren't interested in -// -// NOTE: We're just dipping our toe into the HCI stuff here, so we only care about command complete and status events. This -// isn't the most robust way to do things, but it is effective. -bool HciAdapter::filterAndValidateEvents(uint16_t commandCode, std::vector &buffer) const -{ - // Chew through each event in the buffer, removing those that are not related to the requested commandCode - std::vector lastGoodEvent; - while (!buffer.empty()) - { - // Get an event pointer into the start of our buffer - ResponseEvent *pEvent = reinterpret_cast(buffer.data()); - - // Validate that there is enough space for this event according to its size - size_t dataLength = sizeof(Header) + pEvent->header.dataSize; - if (dataLength > buffer.size()) - { - Logger::error(" + Not enough data for the current event"); - return false; - } - - // !HACK! - If the device is connected, then powering off (Command Code 0x0005) returns a Device Disconnect Event (Event - // Code 0xC) rather than a Command Complete Event (0x1) like it should. - // - // We'll fake it here, converting that condition into a Command Complete Event (0x1). - if (commandCode == 0x0005 && pEvent->header.code == 0xC) - { - Logger::debug("!HACK! Converting invalid Disconect Event to Command Complete Event for power-off command"); - pEvent->header.code = 1; - buffer = {0x1, 0x0, 0x0, 0x0, 0x7, 0x0, 0x5, 0x0, 0x0, 0x2, 0x6, 0x0, 0x0}; - pEvent = reinterpret_cast(buffer.data()); - dataLength = sizeof(Header) + pEvent->header.dataSize; - } - - // Check the event type - if (pEvent->header.code < kMinEventType || pEvent->header.code > kMaxEventType) - { - Logger::error(SSTR << " + Unknown event type " << Utils::hex(pEvent->header.code) << " - ditching all response data to resynchronize"); - return false; - } - - std::string eventTypeName = kEventTypeNames[pEvent->header.code - kMinEventType]; - std::string commandCodeName = pEvent->commandCode < kMinCommandCode || pEvent->commandCode > kMaxCommandCode ? "Unknown" : kCommandCodeNames[pEvent->commandCode - kMinCommandCode]; - - Logger::debug(SSTR << " + Received event type " << Utils::hex(pEvent->header.code) << " (" << eventTypeName << ")"); - - // Success event for our command? - if (pEvent->header.code != 1 && pEvent->commandCode != commandCode) - { - Logger::debug(SSTR << " + Skipping event type " << Utils::hex(pEvent->header.code) << " (" << eventTypeName << ")"); - } - else - { - lastGoodEvent = std::vector(buffer.begin(), buffer.begin() + dataLength); - } - - // Remove the current event and move along - buffer.erase(buffer.begin(), buffer.begin() + dataLength); - } - - // If we don't have a last good event, return an error - if (lastGoodEvent.empty()) - { - return false; - } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // Return the last good event - buffer = lastGoodEvent; return true; } diff --git a/src/HciAdapter.h b/src/HciAdapter.h index b5e8ee7..a19d395 100644 --- a/src/HciAdapter.h +++ b/src/HciAdapter.h @@ -36,6 +36,7 @@ #include #include +#include #include "HciSocket.h" #include "Utils.h" @@ -46,7 +47,53 @@ class HciAdapter { public: - struct Header + // + // Constants + // + + // A constant referring to a 'non-controller' (for commands that do not require a controller index) + static const uint16_t kNonController = 0xffff; + + // Command code names + static const int kMinCommandCode = 0x0001; + static const int kMaxCommandCode = 0x0043; + static const char * const kCommandCodeNames[kMaxCommandCode + 1]; + + // Event type names + static const int kMinEventType = 0x0001; + static const int kMaxEventType = 0x0025; + static const char * const kEventTypeNames[kMaxEventType + 1]; + + static const int kMinStatusCode = 0x00; + static const int kMaxStatusCode = 0x14; + static const char * const kStatusCodes[kMaxStatusCode + 1]; + + // + // Types + // + + // HCI Controller Settings + enum HciControllerSettings + { + EHciPowered = (1<<0), + EHciConnectable = (1<<1), + EHciFastConnectable = (1<<2), + EHciDiscoverable = (1<<3), + EHciBondable = (1<<4), + EHciLinkLevelSecurity = (1<<5), + EHciSecureSimplePairing = (1<<6), + EHciBasicRate_EnhancedDataRate = (1<<7), + EHciHighSpeed = (1<<8), + EHciLowEnergy = (1<<9), + EHciAdvertising = (1<<10), + EHciSecureConnections = (1<<11), + EHciDebugKeys = (1<<12), + EHciPrivacy = (1<<13), + EHciControllerConfiguration = (1<<14), + EHciStaticAddress = (1<<15) + }; + + struct HciHeader { uint16_t code; uint16_t controllerId; @@ -66,11 +113,51 @@ public: dataSize = Utils::endianToHost(dataSize); } + std::string debugText() + { + std::string text = ""; + text += "> Request header\n"; + text += " + Command code : " + Utils::hex(code) + " (" + HciAdapter::kCommandCodeNames[code] + ")\n"; + text += " + Controller Id : " + Utils::hex(controllerId) + "\n"; + text += " + Data size : " + std::to_string(dataSize) + " bytes"; + return text; + } + } __attribute__((packed)); + + struct CommandCompleteEvent + { + HciHeader header; + uint16_t commandCode; + uint8_t status; + + void toNetwork() + { + header.toNetwork(); + commandCode = Utils::endianToHci(commandCode); + } + + void toHost() + { + header.toHost(); + commandCode = Utils::endianToHost(commandCode); + } + + std::string debugText() + { + std::string text = ""; + text += "> Command complete event\n"; + text += " + Event code : " + Utils::hex(header.code) + " (" + HciAdapter::kEventTypeNames[header.code] + ")\n"; + text += " + Controller Id : " + Utils::hex(header.controllerId) + "\n"; + text += " + Data size : " + std::to_string(header.dataSize) + " bytes\n"; + text += " + Command code : " + Utils::hex(commandCode) + " (" + HciAdapter::kCommandCodeNames[commandCode] + ")\n"; + text += " + Status : " + Utils::hex(status); + return text; + } } __attribute__((packed)); - struct ResponseEvent + struct CommandStatusEvent { - Header header; + HciHeader header; uint16_t commandCode; uint8_t status; @@ -86,8 +173,248 @@ public: commandCode = Utils::endianToHost(commandCode); } + std::string debugText() + { + std::string text = ""; + text += "> Command status event\n"; + text += " + Event code : " + Utils::hex(header.code) + " (" + HciAdapter::kEventTypeNames[header.code] + ")\n"; + text += " + Controller Id : " + Utils::hex(header.controllerId) + "\n"; + text += " + Data size : " + std::to_string(header.dataSize) + " bytes\n"; + text += " + Command code : " + Utils::hex(commandCode) + " (" + HciAdapter::kCommandCodeNames[commandCode] + ")\n"; + text += " + Status : " + Utils::hex(status) + " (" + HciAdapter::kStatusCodes[status] + ")"; + return text; + } + } __attribute__((packed)); + + struct DeviceConnectedEvent + { + HciHeader header; + uint8_t address[6]; + uint8_t addressType; + uint32_t flags; + uint16_t eirDataLength; + + void toNetwork() + { + header.toNetwork(); + flags = Utils::endianToHci(flags); + eirDataLength = Utils::endianToHci(eirDataLength); + } + + void toHost() + { + header.toHost(); + flags = Utils::endianToHost(flags); + eirDataLength = Utils::endianToHost(eirDataLength); + } + + std::string debugText() + { + std::string text = ""; + text += "> DeviceConnected event\n"; + text += " + Event code : " + Utils::hex(header.code) + " (" + HciAdapter::kEventTypeNames[header.code] + ")\n"; + text += " + Controller Id : " + Utils::hex(header.controllerId) + "\n"; + text += " + Data size : " + std::to_string(header.dataSize) + " bytes\n"; + text += " + Address : " + Utils::bluetoothAddressString(address) + "\n"; + text += " + Address type : " + Utils::hex(addressType) + "\n"; + text += " + Flags : " + Utils::hex(flags) + "\n"; + text += " + EIR Data Length : " + Utils::hex(eirDataLength); + if (eirDataLength > 0) + { + text += "\n"; + text += " + EIR Data : " + Utils::hex(reinterpret_cast(&eirDataLength) + 2, eirDataLength); + } + return text; + } + } __attribute__((packed)); + + struct DeviceDisconnectedEvent + { + HciHeader header; + uint8_t address[6]; + uint8_t addressType; + uint8_t reason; + + void toNetwork() + { + header.toNetwork(); + } + + void toHost() + { + header.toHost(); + } + + std::string debugText() + { + std::string text = ""; + text += "> DeviceDisconnected event\n"; + text += " + Event code : " + Utils::hex(header.code) + " (" + HciAdapter::kEventTypeNames[header.code] + ")\n"; + text += " + Controller Id : " + Utils::hex(header.controllerId) + "\n"; + text += " + Data size : " + std::to_string(header.dataSize) + " bytes\n"; + text += " + Address : " + Utils::bluetoothAddressString(address) + "\n"; + text += " + Address type : " + Utils::hex(addressType) + "\n"; + text += " + Reason : " + Utils::hex(reason); + return text; + } + } __attribute__((packed)); + + struct AdapterSettings + { + uint32_t masks; + + void toHost() + { + masks = Utils::endianToHost(masks); + } + + bool isSet(HciControllerSettings mask) + { + return (masks & mask) != 0; + } + + // Returns a human-readable string of flags and settings + std::string toString() + { + std::string text = ""; + if (isSet(EHciPowered)) { text += "Powered, "; } + if (isSet(EHciConnectable)) { text += "Connectable, "; } + if (isSet(EHciFastConnectable)) { text += "FC, "; } + if (isSet(EHciDiscoverable)) { text += "Discov, "; } + if (isSet(EHciBondable)) { text += "Bondable, "; } + if (isSet(EHciLinkLevelSecurity)) { text += "LLS, "; } + if (isSet(EHciSecureSimplePairing)) { text += "SSP, "; } + if (isSet(EHciBasicRate_EnhancedDataRate)) { text += "BR/EDR, "; } + if (isSet(EHciHighSpeed)) { text += "HS, "; } + if (isSet(EHciLowEnergy)) { text += "LE, "; } + if (isSet(EHciAdvertising)) { text += "Adv, "; } + if (isSet(EHciSecureConnections)) { text += "SC, "; } + if (isSet(EHciDebugKeys)) { text += "DebugKeys, "; } + if (isSet(EHciPrivacy)) { text += "Privacy, "; } + if (isSet(EHciControllerConfiguration)) { text += "ControllerConfig, "; } + if (isSet(EHciStaticAddress)) { text += "StaticAddr, "; } + + if (text.length() != 0) + { + text = text.substr(0, text.length() - 2); + } + + return text; + } + + std::string debugText() + { + std::string text = ""; + text += "> Adapter settings\n"; + text += " + " + toString(); + return text; + } } __attribute__((packed)); + // The comments documenting these fields are very high level. There is a lot of detailed information not present, for example + // some values are not available at all times. This is fully documented in: + // + // https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt + struct ControllerInformation + { + uint8_t address[6]; // The Bluetooth address + uint8_t bluetoothVersion; // Bluetooth version + uint16_t manufacturer; // The manufacturer + AdapterSettings supportedSettings; // Bits for various supported settings (see HciControllerSettings) + AdapterSettings currentSettings; // Bits for various currently configured settings (see HciControllerSettings) + uint8_t classOfDevice[3]; // Um, yeah. That. + char name[249]; // Null terminated name + char shortName[11]; // Null terminated short name + + void toHost() + { + manufacturer = Utils::endianToHost(manufacturer); + supportedSettings.toHost(); + currentSettings.toHost(); + } + + std::string debugText() + { + std::string text = ""; + text += "> Controller information\n"; + text += " + Current settings : " + Utils::hex(currentSettings.masks) + "\n"; + text += " + Address : " + Utils::bluetoothAddressString(address) + "\n"; + text += " + BT Version : " + std::to_string(static_cast(bluetoothVersion)) + "\n"; + text += " + Manufacturer : " + Utils::hex(manufacturer) + "\n"; + text += " + Supported settings : " + supportedSettings.toString() + "\n"; + text += " + Current settings : " + currentSettings.toString() + "\n"; + text += " + Name : " + std::string(name) + "\n"; + text += " + Short name : " + std::string(shortName); + return text; + } + } __attribute__((packed)); + + struct VersionInformation + { + uint8_t version; + uint16_t revision; + + void toHost() + { + revision = Utils::endianToHost(revision); + } + + std::string debugText() + { + std::string text = ""; + text += "> Version information\n"; + text += " + Version : " + std::to_string(static_cast(version)) + "\n"; + text += " + Revision : " + std::to_string(revision); + return text; + } + } __attribute__((packed)); + + struct LocalName + { + char name[249]; + char shortName[11]; + + std::string debugText() + { + std::string text = ""; + text += "> Local name information\n"; + text += " + Name : '" + std::string(name) + "\n"; + text += " + Short name : '" + std::string(shortName); + return text; + } + } __attribute__((packed)); + + // + // Accessors + // + + // Returns the instance to this singleton class + static HciAdapter &getInstance() + { + static HciAdapter instance; + return instance; + } + + AdapterSettings getAdapterSettings(); + ControllerInformation getControllerInformation(); + VersionInformation getVersionInformation(); + LocalName getLocalName(); + + int getActiveConnectionCount() { return activeConnections; } + + // + // Disallow copies of our singleton (c++11) + // + + HciAdapter(HciAdapter const&) = delete; + void operator=(HciAdapter const&) = delete; + + // Reads current values from the controller + // + // This effectively requests data from the controller but that data may not be available instantly, but within a few + // milliseconds. Therefore, it is not recommended attempt to retrieve the results from their accessors immediately. + void sync(uint16_t controllerIndex); + // Connects the HCI socket if a connection does not already exist // // If a connection already exists, this method will do nothing and return true. @@ -115,28 +442,31 @@ public: // a failure is returned. // // Returns true on success, otherwise false - bool sendCommand(Header &request, ResponseEvent &response, int responseLen); + bool sendCommand(HciHeader &request); -private: - - // Reads a response from the HCI socket + // Event processor, responsible for receiving events from the HCI socket // - // Responses are generally triggered by sending commands (see `sendCommand`) but not always. In HCI parlance, a response is - // actually an event. Performing commands triggers events. There is not always a 1:1 ratio betwee command and event, and a - // command may trigger different events based on the result of the command. - // - // Unlike the other methods in this class, this method does not auto-connect, as this method is private and can only be called - // from methods that have alreay auto-connected. - bool readResponse(uint16_t commandCode, ResponseEvent &response, size_t responseLen) const; + // This mehtod should not be called directly. Rather, it runs continuously on a thread until the server shuts down + void runEventThread(); - // Filter out events that we aren't interested in - // - // NOTE: We're just dipping our toe into the HCI stuff here, so we only care about command complete and status events. This - // isn't the most robust way to do things, but it is effective. - bool filterAndValidateEvents(uint16_t commandCode, std::vector &buffer) const; +private: + // Private constructor for our Singleton + HciAdapter() : activeConnections(0) {} // Our HCI Socket, which allows us to talk directly to the kernel HciSocket hciSocket; + + // Our event thread listens for events coming from the adapter and deals with them appropriately + static std::thread eventThread; + + // Our adapter information + AdapterSettings adapterSettings; + ControllerInformation controllerInformation; + VersionInformation versionInformation; + LocalName localName; + + // Our active connection count + int activeConnections; }; }; // namespace ggk \ No newline at end of file diff --git a/src/HciSocket.cpp b/src/HciSocket.cpp index c3bcc3e..214ef85 100644 --- a/src/HciSocket.cpp +++ b/src/HciSocket.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include "HciSocket.h" #include "Logger.h" @@ -114,55 +115,50 @@ void HciSocket::disconnect() // Reads data from the HCI socket // -// Raw data is read until no more data is available. If no data is available when this method initially starts to read, it will -// retry for a maximum timeout defined by `kMaxRetryTimeMS`. +// Raw data is read and returned in `response`. // -// Returns true if any data was read successfully, otherwise false is returned in the case of an error or a timeout. +// Returns true if data was read successfully, otherwise false is returned. A false return code does not necessarily depict +// an error, as this can arise from expected conditions (such as an interrupt.) bool HciSocket::read(std::vector &response) const { - // Clear out our response - response.clear(); + // Fill our response with empty data + response.resize(kResponseMaxSize, 0); - uint8_t responseChunk[kResponseChunkSize]; + // Ensure we have blocking I/O + fcntl(fdSocket, F_SETFL, 0); - int retryTimeMS = 0; - while (retryTimeMS < kMaxRetryTimeMS && !ggkIsServerRunning()) + // Block until we receive data, a disconnect, or a signal + ssize_t bytesRead = ::recv(fdSocket, &response[0], kResponseMaxSize, MSG_WAITALL); + + // If there was an error, wipe the data and return an error condition + if (bytesRead < 0) { - ssize_t bytesRead = ::read(fdSocket, responseChunk, kResponseChunkSize); - if (bytesRead > 0) + if (errno == EINTR) { - if (response.size() + bytesRead > kResponseMaxSize) - { - Logger::warn(SSTR << "Response has exceeded maximum size"); - return false; - } - // We just received some data, add it to our buffer - std::vector insertBuf(responseChunk, responseChunk + bytesRead); - response.insert(response.end(), insertBuf.begin(), insertBuf.end()); + Logger::debug("HciSocket receive interrupted"); } else { - // If we have data, we're at the end - if (response.size() != 0) - { - break; - } + logErrno("recv"); } - - // Retry (or continue reading) - std::this_thread::sleep_for(std::chrono::milliseconds(kRetryIntervalMS)); - retryTimeMS += kRetryIntervalMS; + response.resize(0); + return false; } - - // Did we time out? - if (retryTimeMS >= kMaxRetryTimeMS) + else if (bytesRead == 0) { - logErrno("read(header)"); + Logger::error(" + Peer closed the socket"); + response.resize(0); return false; } - Logger::debug(SSTR << " + Read " << response.size() << " bytes"); - Logger::debug(SSTR << Utils::hex(response.data(), response.size())); + // We have data + response.resize(bytesRead); + + std::string dump = ""; + dump += "> Read " + std::to_string(response.size()) + " bytes\n"; + dump += Utils::hex(response.data(), response.size()); + Logger::debug(dump); + return true; } @@ -179,8 +175,10 @@ bool HciSocket::write(std::vector buffer) const // This method returns true if the bytes were written successfully, otherwise false bool HciSocket::write(const uint8_t *pBuffer, size_t count) const { - Logger::debug(SSTR << " + Writing " << count << " bytes"); - Logger::debug(SSTR << Utils::hex(pBuffer, count)); + std::string dump = ""; + dump += "> Writing " + std::to_string(count) + " bytes\n"; + dump += Utils::hex(pBuffer, count); + Logger::debug(dump); size_t len = ::write(fdSocket, pBuffer, count); @@ -199,4 +197,4 @@ void HciSocket::logErrno(const char *pOperation) const Logger::error(SSTR << "" << pOperation << " on Bluetooth management socket error (" << errno << "): " << strerror(errno)); } -}; // namespace ggk \ No newline at end of file +}; // namespace ggk diff --git a/src/HciSocket.h b/src/HciSocket.h index d968dc5..20dbe4e 100644 --- a/src/HciSocket.h +++ b/src/HciSocket.h @@ -86,9 +86,6 @@ private: int fdSocket; const size_t kResponseMaxSize = 64 * 1024; - const int kResponseChunkSize = 1024; - const int kRetryIntervalMS = 20; - const int kMaxRetryTimeMS = 5000; }; }; // namespace ggk \ No newline at end of file diff --git a/src/Init.cpp b/src/Init.cpp index 9be0ee2..49a4866 100644 --- a/src/Init.cpp +++ b/src/Init.cpp @@ -298,6 +298,9 @@ void shutdown() // Our new state: shutting down setServerRunState(EStopping); + // Disconnect our HciAdapter + HciAdapter::getInstance().disconnect(); + // If we still have a main loop, ask it to quit if (nullptr != pMainLoop) { @@ -487,11 +490,17 @@ gboolean onSetProperty // // --------------------------------------------------------------------------------------------------------------------------------- +// Convenience method for setting a retry timer so that operations can be continuously retried until we eventually succeed +void setRetry() +{ + retryTimeStart = time(nullptr); +} + // Convenience method for setting a retry timer so that failures (related to initialization) can be continuously retried until we // eventually succeed. void setRetryFailure() { - retryTimeStart = time(nullptr); + setRetry(); Logger::warn(SSTR << " + Will retry the failed operation in about " << kRetryDelaySeconds << " seconds"); } @@ -663,127 +672,105 @@ void configureAdapter() { Mgmt mgmt; + // Get our properly truncated advertising names + std::string advertisingName = Mgmt::truncateName(TheServer->getAdvertisingName()); + std::string advertisingShortName = Mgmt::truncateShortName(TheServer->getAdvertisingShortName()); + // Find out what our current settings are - Logger::debug(SSTR << "Getting device information"); - Mgmt::ControllerInformation *pInfo = mgmt.getControllerInformation(); - if (nullptr == pInfo) + HciAdapter::ControllerInformation info = HciAdapter::getInstance().getControllerInformation(); + + // Are all of our settings the way we want them? + bool pwFlag = info.currentSettings.isSet(HciAdapter::EHciPowered) == true; + bool leFlag = info.currentSettings.isSet(HciAdapter::EHciLowEnergy) == true; + bool brFlag = info.currentSettings.isSet(HciAdapter::EHciBasicRate_EnhancedDataRate) == TheServer->getEnableBREDR(); + bool scFlag = info.currentSettings.isSet(HciAdapter::EHciSecureConnections) == TheServer->getEnableSecureConnection(); + bool bnFlag = info.currentSettings.isSet(HciAdapter::EHciBondable) == TheServer->getEnableBondable(); + bool cnFlag = info.currentSettings.isSet(HciAdapter::EHciConnectable) == TheServer->getEnableConnectable(); + bool adFlag = info.currentSettings.isSet(HciAdapter::EHciAdvertising) == TheServer->getEnableAdvertising(); + bool anFlag = (advertisingName.length() == 0 || advertisingName == info.name) && (advertisingShortName.length() == 0 || advertisingShortName == info.shortName); + + // If everything is setup already, we're done + if (pwFlag && leFlag && brFlag && scFlag && bnFlag && cnFlag && adFlag && anFlag) { - setRetryFailure(); + Logger::info("The adapter is fully configured"); + + // We're all set, nothing to do! + bAdapterConfigured = true; + initializationStateProcessor(); return; } // We need it off to start with - if ((pInfo->currentSettings & Mgmt::EHciPowered) != 0) + if (pwFlag) { - Logger::debug(SSTR << "Powering off"); - if (!mgmt.setPowered(false)) - { - setRetryFailure(); - return; - } + Logger::debug("Powering off"); + mgmt.setPowered(false); + } + + // Enable the LE state (we always set this state if it's not set) + if (!leFlag) + { + Logger::debug("Enabling LE"); + mgmt.setLE(true); } // Change the Br/Edr state? - bool bredrCurrentState = (pInfo->currentSettings & Mgmt::EHciBasicRate_EnhancedDataRate) != 0 ? true:false; - if (TheServer->getEnableBREDR() != bredrCurrentState) + // + // Note that enabling this requries LE to already be enabled or this command will receive a 'rejected' result + if (!brFlag) { Logger::debug(SSTR << (TheServer->getEnableBREDR() ? "Enabling":"Disabling") << " BR/EDR"); - if (!mgmt.setBredr(TheServer->getEnableBREDR())) - { - setRetryFailure(); - return; - } + mgmt.setBredr(TheServer->getEnableBREDR()); } // Change the Secure Connectinos state? - bool scCurrentState = (pInfo->currentSettings & Mgmt::EHciSecureConnections) != 0 ? true:false; - if (TheServer->getEnableSecureConnection() != scCurrentState) + if (!scFlag) { Logger::debug(SSTR << (TheServer->getEnableSecureConnection() ? "Enabling":"Disabling") << " Secure Connections"); - if (!mgmt.setSecureConnections(TheServer->getEnableSecureConnection() ? 1 : 0)) - { - setRetryFailure(); - return; - } + mgmt.setSecureConnections(TheServer->getEnableSecureConnection() ? 1 : 0); } // Change the Bondable state? - bool bondableCurrentState = (pInfo->currentSettings & Mgmt::EHciBondable) != 0 ? true:false; - if (TheServer->getEnableBondable() != bondableCurrentState) + if (!bnFlag) { Logger::debug(SSTR << (TheServer->getEnableBondable() ? "Enabling":"Disabling") << " Bondable"); - if (!mgmt.setBondable(TheServer->getEnableBondable())) - { - setRetryFailure(); - return; - } + mgmt.setBondable(TheServer->getEnableBondable()); } // Change the Connectable state? - bool connectableCurrentState = (pInfo->currentSettings & Mgmt::EHciConnectable) != 0 ? true:false; - if (TheServer->getEnableConnectable() != connectableCurrentState) + if (!cnFlag) { Logger::debug(SSTR << (TheServer->getEnableConnectable() ? "Enabling":"Disabling") << " Connectable"); - if (!mgmt.setConnectable(TheServer->getEnableConnectable())) - { - setRetryFailure(); - return; - } - } - - // Enable the LE state (we always set this state if it's not set) - if ((pInfo->currentSettings & Mgmt::EHciLowEnergy) == 0) - { - Logger::debug(SSTR << "Enabling LE"); - if (!mgmt.setLE(true)) - { - setRetryFailure(); - return; - } + mgmt.setConnectable(TheServer->getEnableConnectable()); } // Change the Advertising state? - bool advertisingCurrentState = (pInfo->currentSettings & Mgmt::EHciAdvertising) != 0 ? true:false; - if (TheServer->getEnableAdvertising() != advertisingCurrentState) + if (!adFlag) { Logger::debug(SSTR << (TheServer->getEnableAdvertising() ? "Enabling":"Disabling") << " Advertising"); - if (!mgmt.setAdvertising(TheServer->getEnableAdvertising() ? 1 : 0)) - { - setRetryFailure(); - return; - } + mgmt.setAdvertising(TheServer->getEnableAdvertising() ? 1 : 0); } // Set the name? - if (TheServer->getAdvertisingName().length() != 0 || TheServer->getAdvertisingShortName().length() != 0) + if (!anFlag) { - std::string advertisingName = Mgmt::truncateName(TheServer->getAdvertisingName()); - std::string advertisingShortName = Mgmt::truncateShortName(TheServer->getAdvertisingShortName()); - - if (advertisingName != pInfo->name || advertisingShortName != pInfo->shortName) - { - Logger::info(SSTR << "Setting advertising name to '" << advertisingName << "' (with short name: '" << advertisingShortName << "')"); - if (!mgmt.setName(advertisingName.c_str(), advertisingShortName.c_str())) - { - setRetryFailure(); - return; - } - } + Logger::info(SSTR << "Setting advertising name to '" << advertisingName << "' (with short name: '" << advertisingShortName << "')"); + mgmt.setName(advertisingName.c_str(), advertisingShortName.c_str()); } // Turn it back on - Logger::debug(SSTR << "Powering on"); - if (!mgmt.setPowered(true)) - { - setRetryFailure(); - return; - } + Logger::debug("Powering on"); + mgmt.setPowered(true); + + // Give it some extra time to power up - this shouldn't really be necessary, but it won't hurt + std::this_thread::sleep_for(std::chrono::milliseconds(500)); // We can ignore errors on this - we're just letting it dump the output - mgmt.getControllerInformation(); + HciAdapter::getInstance().getControllerInformation(); - bAdapterConfigured = true; - initializationStateProcessor(); + // We always set the retry (silently) so we can validate our settings once the adapter has had a chance to respond to all + // of our requests + setRetry(); } // --------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/Mgmt.cpp b/src/Mgmt.cpp index 34537f0..e61653e 100644 --- a/src/Mgmt.cpp +++ b/src/Mgmt.cpp @@ -48,92 +48,7 @@ namespace ggk { Mgmt::Mgmt(uint16_t controllerIndex) : controllerIndex(controllerIndex) { -} - -// Returns the version information: -// -// bits 0-15 = revision -// bits 16-23 = version -// -// ... or -1 on error -int Mgmt::getVersion() -{ - struct SResponse : HciAdapter::ResponseEvent - { - uint8_t version; - uint16_t revision; - - void toHost() - { - revision = Utils::endianToHost(revision); - } - } __attribute__((packed)); - - HciAdapter::Header request; - request.code = 1; - request.controllerId = kNonController; - request.dataSize = 0; - - SResponse response; - if (!hciAdapter.sendCommand(request, response, sizeof(response))) - { - Logger::warn(SSTR << " + Failed to get version information"); - return -1; - } - - response.toHost(); - - Logger::debug(SSTR << " + Version response has version=" << Utils::hex(response.version) << " and revision=" << Utils::hex(response.revision)); - return (response.version << 16) | response.revision; -} - -// Returns information about the controller (address, name, current settings, etc.) -// -// See the definition for MgmtControllerInformation for details. -// -// ... or nullptr on error -Mgmt::ControllerInformation *Mgmt::getControllerInformation() -{ - struct SResponse : HciAdapter::ResponseEvent - { - ControllerInformation info; - - void toHost() - { - info.toHost(); - } - } __attribute__((packed)); - - HciAdapter::Header request; - request.code = 4; // Controller Information Command - request.controllerId = controllerIndex; - request.dataSize = 0; - - Logger::debug("Dumping device information after configuration..."); - - SResponse response; - if (!hciAdapter.sendCommand(request, response, sizeof(response))) - { - Logger::warn(SSTR << " + Failed to get current settings"); - return nullptr; - } - - response.toHost(); - - // Copy it to our local - controllerInfo = response.info; - - Logger::debug(" + Controller information"); - Logger::debug(SSTR << " + Current settings : " << Utils::hex(controllerInfo.currentSettings)); - Logger::debug(SSTR << " + Address : " << Utils::bluetoothAddressString(controllerInfo.address)); - Logger::debug(SSTR << " + BT Version : " << controllerInfo.bluetoothVersion); - Logger::debug(SSTR << " + Manufacturer : " << Utils::hex(controllerInfo.manufacturer)); - Logger::debug(SSTR << " + Supported settings : " << controllerSettingsString(controllerInfo.supportedSettings)); - Logger::debug(SSTR << " + Current settings : " << controllerSettingsString(controllerInfo.currentSettings)); - Logger::debug(SSTR << " + Name : " << controllerInfo.name); - Logger::debug(SSTR << " + Short name : " << controllerInfo.shortName); - - return &controllerInfo; + HciAdapter::getInstance().sync(controllerIndex); } // Set the adapter name and short name @@ -149,22 +64,16 @@ bool Mgmt::setName(std::string name, std::string shortName) name = truncateName(name); shortName = truncateShortName(shortName); - struct SRequest : HciAdapter::Header - { - char name[249]; - char shortName[11]; - } __attribute__((packed)); - - struct SResponse : HciAdapter::ResponseEvent + struct SRequest : HciAdapter::HciHeader { char name[249]; char shortName[11]; } __attribute__((packed)); SRequest request; - request.code = 0x000F; + request.code = Mgmt::ESetLocalNameCommand; request.controllerId = controllerIndex; - request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::Header); + request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::HciHeader); memset(request.name, 0, sizeof(request.name)); snprintf(request.name, sizeof(request.name), "%s", name.c_str()); @@ -172,14 +81,12 @@ bool Mgmt::setName(std::string name, std::string shortName) memset(request.shortName, 0, sizeof(request.shortName)); snprintf(request.shortName, sizeof(request.shortName), "%s", shortName.c_str()); - SResponse response; - if (!hciAdapter.sendCommand(request, response, sizeof(response))) + if (!HciAdapter::getInstance().sendCommand(request)) { Logger::warn(SSTR << " + Failed to set name"); return false; } - Logger::info(SSTR << " + Name set to '" << request.name << "', short name set to '" << request.shortName << "'"); return true; } @@ -188,39 +95,25 @@ bool Mgmt::setName(std::string name, std::string shortName) // Many settings are set the same way, this is just a convenience routine to handle them all // // Returns true on success, otherwise false -bool Mgmt::setState(const char *pSettingName, uint16_t commandCode, uint16_t controllerId, uint8_t newState) +bool Mgmt::setState(uint16_t commandCode, uint16_t controllerId, uint8_t newState) { - struct SRequest : HciAdapter::Header + struct SRequest : HciAdapter::HciHeader { uint8_t state; } __attribute__((packed)); - struct SResponse : HciAdapter::ResponseEvent - { - uint32_t currentSettings; - - void toHost() - { - currentSettings = Utils::endianToHost(currentSettings); - } - } __attribute__((packed)); - SRequest request; request.code = commandCode; request.controllerId = controllerId; - request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::Header); + request.dataSize = sizeof(SRequest) - sizeof(HciAdapter::HciHeader); request.state = newState; - SResponse response; - if (!hciAdapter.sendCommand(request, response, sizeof(response))) + if (!HciAdapter::getInstance().sendCommand(request)) { - Logger::warn(SSTR << " + Failed to set " << pSettingName << " state to: " << static_cast(newState)); + Logger::warn(SSTR << " + Failed to set " << HciAdapter::kCommandCodeNames[commandCode] << " state to: " << static_cast(newState)); return false; } - response.toHost(); - - Logger::debug(SSTR << " + " << pSettingName << " set to " << static_cast(newState) << ": " << controllerSettingsString(response.currentSettings)); return true; } @@ -229,7 +122,7 @@ bool Mgmt::setState(const char *pSettingName, uint16_t commandCode, uint16_t con // Returns true on success, otherwise false bool Mgmt::setPowered(bool newState) { - return setState("Powered", 0x0005, controllerIndex, newState ? 1 : 0); + return setState(Mgmt::ESetPoweredCommand, controllerIndex, newState ? 1 : 0); } // Set the BR/EDR state to `newState` (true = enabled, false = disabled) @@ -237,7 +130,7 @@ bool Mgmt::setPowered(bool newState) // Returns true on success, otherwise false bool Mgmt::setBredr(bool newState) { - return setState("BR/EDR", 0x002A, controllerIndex, newState ? 1 : 0); + return setState(Mgmt::ESetBREDRCommand, controllerIndex, newState ? 1 : 0); } // Set the Secure Connection state (0 = disabled, 1 = enabled, 2 = secure connections only mode) @@ -245,7 +138,7 @@ bool Mgmt::setBredr(bool newState) // Returns true on success, otherwise false bool Mgmt::setSecureConnections(uint8_t newState) { - return setState("SecureConnections", 0x002D, controllerIndex, newState); + return setState(Mgmt::ESetSecureConnectionsCommand, controllerIndex, newState); } // Set the bondable state to `newState` (true = enabled, false = disabled) @@ -253,7 +146,7 @@ bool Mgmt::setSecureConnections(uint8_t newState) // Returns true on success, otherwise false bool Mgmt::setBondable(bool newState) { - return setState("SecureConnections", 0x0009, controllerIndex, newState ? 1 : 0); + return setState(Mgmt::ESetBondableCommand, controllerIndex, newState ? 1 : 0); } // Set the connectable state to `newState` (true = enabled, false = disabled) @@ -261,7 +154,7 @@ bool Mgmt::setBondable(bool newState) // Returns true on success, otherwise false bool Mgmt::setConnectable(bool newState) { - return setState("Connectable", 0x0007, controllerIndex, newState ? 1 : 0); + return setState(Mgmt::ESetConnectableCommand, controllerIndex, newState ? 1 : 0); } // Set the LE state to `newState` (true = enabled, false = disabled) @@ -269,7 +162,7 @@ bool Mgmt::setConnectable(bool newState) // Returns true on success, otherwise false bool Mgmt::setLE(bool newState) { - return setState("LowEnergy", 0x000D, controllerIndex, newState ? 1 : 0); + return setState(Mgmt::ESetLowEnergyCommand, controllerIndex, newState ? 1 : 0); } // Set the advertising state to `newState` (0 = disabled, 1 = enabled (with consideration towards the connectable setting), @@ -278,44 +171,13 @@ bool Mgmt::setLE(bool newState) // Returns true on success, otherwise false bool Mgmt::setAdvertising(uint8_t newState) { - return setState("Advertising", 0x0029, controllerIndex, newState); + return setState(Mgmt::ESetAdvertisingCommand, controllerIndex, newState); } // --------------------------------------------------------------------------------------------------------------------------------- // Utilitarian // --------------------------------------------------------------------------------------------------------------------------------- -// Transforms a "Current_Settings" value (4 octets as defined by the Bluetooth Management API specification) into a human-readable -// string of flags and settings. -std::string Mgmt::controllerSettingsString(uint32_t bits) -{ - std::string result = ""; - - if ((bits & EHciPowered) != 0) { result += "Powered, "; } - if ((bits & EHciConnectable) != 0) { result += "Connectable, "; } - if ((bits & EHciFastConnectable) != 0) { result += "FC, "; } - if ((bits & EHciDiscoverable) != 0) { result += "Discov, "; } - if ((bits & EHciBondable) != 0) { result += "Bondable, "; } - if ((bits & EHciLinkLevelSecurity) != 0) { result += "LLS, "; } - if ((bits & EHciSecureSimplePairing) != 0) { result += "SSP, "; } - if ((bits & EHciBasicRate_EnhancedDataRate) != 0) { result += "BR/EDR, "; } - if ((bits & EHciHighSpeed) != 0) { result += "HS, "; } - if ((bits & EHciLowEnergy) != 0) { result += "LE, "; } - if ((bits & EHciAdvertising) != 0) { result += "Adv, "; } - if ((bits & EHciSecureConnections) != 0) { result += "SC, "; } - if ((bits & EHciDebugKeys) != 0) { result += "DebugKeys, "; } - if ((bits & EHciPrivacy) != 0) { result += "Privacy, "; } - if ((bits & EHciControllerConfiguration) != 0) { result += "ControllerConfig, "; } - if ((bits & EHciStaticAddress) != 0) { result += "StaticAddr, "; } - - if (result.length() != 0) - { - result = result.substr(0, result.length() - 2); - } - - return result; -} - // Truncates the string `name` to the maximum allowed length for an adapter name. If `name` needs no truncation, a copy of // `name` is returned. std::string Mgmt::truncateName(const std::string &name) diff --git a/src/Mgmt.h b/src/Mgmt.h index 23315a5..196eb1a 100644 --- a/src/Mgmt.h +++ b/src/Mgmt.h @@ -59,49 +59,121 @@ struct Mgmt // Types // - // HCI Controller Settings - enum EHciControllerSettings + // These indices should match those in HciAdapter::kEventTypeNames + enum EventTypes { - EHciPowered = (1<<0), - EHciConnectable = (1<<1), - EHciFastConnectable = (1<<2), - EHciDiscoverable = (1<<3), - EHciBondable = (1<<4), - EHciLinkLevelSecurity = (1<<5), - EHciSecureSimplePairing = (1<<6), - EHciBasicRate_EnhancedDataRate = (1<<7), - EHciHighSpeed = (1<<8), - EHciLowEnergy = (1<<9), - EHciAdvertising = (1<<10), - EHciSecureConnections = (1<<11), - EHciDebugKeys = (1<<12), - EHciPrivacy = (1<<13), - EHciControllerConfiguration = (1<<14), - EHciStaticAddress = (1<<15) + EInvalidEvent = 0x0000, + ECommandCompleteEvent = 0x0001, + ECommandStatusEvent = 0x0002, + EControllerErrorEvent = 0x0003, + EIndexAddedEvent = 0x0004, + EIndexRemovedEvent = 0x0005, + ENewSettingsEvent = 0x0006, + EClassOfDeviceChangedEvent = 0x0007, + ELocalNameChangedEvent = 0x0008, + ENewLinkKeyEvent = 0x0009, + ENewLongTermKeyEvent = 0x000A, + EDeviceConnectedEvent = 0x000B, + EDeviceDisconnectedEvent = 0x000C, + EConnectFailedEvent = 0x000D, + EPINCodeRequestEvent = 0x000E, + EUserConfirmationRequestEvent = 0x000F, + EUserPasskeyRequestEvent = 0x0010, + EAuthenticationFailedEvent = 0x0011, + EDeviceFoundEvent = 0x0012, + EDiscoveringEvent = 0x0013, + EDeviceBlockedEvent = 0x0014, + EDeviceUnblockedEvent = 0x0015, + EDeviceUnpairedEvent = 0x0016, + EPasskeyNotifyEvent = 0x0017, + ENewIdentityResolvingKeyEvent = 0x0018, + ENewSignatureResolvingKeyEvent = 0x0019, + EDeviceAddedEvent = 0x001a, + EDeviceRemovedEvent = 0x001b, + ENewConnectionParameterEvent = 0x001c, + EUnconfiguredIndexAddedEvent = 0x001d, + EUnconfiguredIndexRemovedEvent = 0x001e, + ENewConfigurationOptionsEvent = 0x001f, + EExtendedIndexAddedEvent = 0x0020, + EExtendedIndexRemovedEvent = 0x0021, + ELocalOutOfBandExtendedDataUpdatedEvent = 0x0022, + EAdvertisingAddedEvent = 0x0023, + EAdvertisingRemovedEvent = 0x0024, + EExtendedControllerInformationChangedEvent = 0x0025 }; - // The comments documenting these fields are very high level. There is a lot of detailed information not present, for example - // some values are not available at all times. This is fully documented in: - // - // https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt - struct ControllerInformation + // These indices should match those in HciAdapter::kCommandCodeNames + enum CommandCodes { - uint8_t address[6]; // The Bluetooth address - uint8_t bluetoothVersion; // Bluetooth version - uint16_t manufacturer; // The manufacturer - uint32_t supportedSettings; // Bits for various supported settings (see EHciControllerSettings) - uint32_t currentSettings; // Bits for various currently configured settings (see EHciControllerSettings) - uint8_t classOfDevice[3]; // Um, yeah. That. - char name[249]; // Null terminated name - char shortName[11]; // Null terminated short name - - void toHost() - { - manufacturer = Utils::endianToHost(manufacturer); - supportedSettings = Utils::endianToHost(supportedSettings); - currentSettings = Utils::endianToHost(currentSettings); - } - } __attribute__((packed)); + EInvalidCommand = 0x0000, + EReadVersionInformationCommand = 0x0001, + EReadSupportedCommandsCommand = 0x0002, + EReadControllerIndexListCommand = 0x0003, + EReadControllerInformationCommand = 0x0004, + ESetPoweredCommand = 0x0005, + ESetDiscoverableCommand = 0x0006, + ESetConnectableCommand = 0x0007, + ESetFastConnectableCommand = 0x0008, + ESetBondableCommand = 0x0009, + ESetLinkSecurityCommand = 0x000A, + ESetSecureSimplePairingCommand = 0x000B, + ESetHighSpeedCommand = 0x000C, + ESetLowEnergyCommand = 0x000D, + ESetDeviceClass = 0x000E, + ESetLocalNameCommand = 0x000F, + EAddUUIDCommand = 0x0010, + ERemoveUUIDCommand = 0x0011, + ELoadLinkKeysCommand = 0x0012, + ELoadLongTermKeysCommand = 0x0013, + EDisconnectCommand = 0x0014, + EGetConnectionsCommand = 0x0015, + EPINCodeReplyCommand = 0x0016, + EPINCodeNegativeReplyCommand = 0x0017, + ESetIOCapabilityCommand = 0x0018, + EPairDeviceCommand = 0x0019, + ECancelPairDeviceCommand = 0x001A, + EUnpairDeviceCommand = 0x001B, + EUserConfirmationReplyCommand = 0x001C, + EUserConfirmationNegativeReplyCommand = 0x001D, + EUserPasskeyReplyCommand = 0x001E, + EUserPasskeyNegativeReplyCommand = 0x001F, + EReadLocalOutOfBandDataCommand = 0x0020, + EAddRemoteOutOfBandDataCommand = 0x0021, + ERemoveRemoteOutOfBandDataCommand = 0x0022, + EStartDiscoveryCommand = 0x0023, + EStopDiscoveryCommand = 0x0024, + EConfirmNameCommand = 0x0025, + EBlockDeviceCommand = 0x0026, + EUnblockDeviceCommand = 0x0027, + ESetDeviceIDCommand = 0x0028, + ESetAdvertisingCommand = 0x0029, + ESetBREDRCommand = 0x002A, + ESetStaticAddressCommand = 0x002B, + ESetScanParametersCommand = 0x002C, + ESetSecureConnectionsCommand = 0x002D, + ESetDebugKeysCommand = 0x002E, + ESetPrivacyCommand = 0x002F, + ELoadIdentityResolvingKeysCommand = 0x0030, + EGetConnectionInformationCommand = 0x0031, + EGetClockInformationCommand = 0x0032, + EAddDeviceCommand = 0x0033, + ERemoveDeviceCommand = 0x0034, + ELoadConnectionParametersCommand = 0x0035, + EReadUnconfiguredControllerIndexListCommand = 0x0036, + EReadControllerConfigurationInformationCommand = 0x0037, + ESetExternalConfigurationCommand = 0x0038, + ESetPublicAddressCommand = 0x0039, + EStartServiceDiscoveryCommand = 0x003a, + EReadLocalOutOfBandExtendedDataCommand = 0x003b, + EReadExtendedControllerIndexListCommand = 0x003c, + EReadAdvertisingFeaturesCommand = 0x003d, + EAddAdvertisingCommand = 0x003e, + ERemoveAdvertisingCommand = 0x003f, + EGetAdvertisingSizeInformationCommand = 0x0040, + EStartLimitedDiscoveryCommand = 0x0041, + EReadExtendedControllerInformationCommand = 0x0042, + ESetAppearanceCommand = 0x0043 + }; // Construct the Mgmt device // @@ -109,21 +181,6 @@ struct Mgmt // of the first device (0) will be used. Mgmt(uint16_t controllerIndex = kDefaultControllerIndex); - // Returns the version information: - // - // bits 0-15 = revision - // bits 16-23 = version - // - // ... or -1 on error - int getVersion(); - - // Returns information about the controller (address, name, current settings, etc.) - // - // See the definition for MgmtControllerInformation for details. - // - // ... or nullptr on error - ControllerInformation *getControllerInformation(); - // Set the adapter name and short name // // The inputs `name` and `shortName` may be truncated prior to setting them on the adapter. To ensure that `name` and @@ -138,7 +195,7 @@ struct Mgmt // Many settings are set the same way, this is just a convenience routine to handle them all // // Returns true on success, otherwise false - bool setState(const char *pSettingName, uint16_t commandCode, uint16_t controllerId, uint8_t newState); + bool setState(uint16_t commandCode, uint16_t controllerId, uint8_t newState); // Set the powered state to `newState` (true = powered on, false = powered off) // @@ -180,10 +237,6 @@ struct Mgmt // Utilitarian // - // Transforms a "Current_Settings" value (4 octets as defined by the Bluetooth Management API specification) into a human- - // readable string of flags and settings. - static std::string controllerSettingsString(uint32_t bits); - // Truncates the string `name` to the maximum allowed length for an adapter name. If `name` needs no truncation, a copy of // `name` is returned. static std::string truncateName(const std::string &name); @@ -198,20 +251,11 @@ private: // Data members // - // Our adapter - we use this to connect to the HCI socket to talk to the adapter - HciAdapter hciAdapter; - - // Our controller information, updated each time the controller information is received - ControllerInformation controllerInfo; - // The default controller index (the first device) uint16_t controllerIndex; // Default controller index static const uint16_t kDefaultControllerIndex = 0; - - // A constant referring to a 'non-controller' (for commands that do not require a controller index) - static const uint16_t kNonController = 0xffff; }; }; // namespace ggk \ No newline at end of file diff --git a/src/TickEvent.h b/src/TickEvent.h index e72ae9b..146f670 100644 --- a/src/TickEvent.h +++ b/src/TickEvent.h @@ -129,7 +129,7 @@ struct TickEvent { if (nullptr != callback) { - Logger::info(SSTR << "Ticking at path '" << path << "'"); + Logger::debug(SSTR << "Ticking at path '" << path << "'"); callback(*static_cast(pOwner), *this, pConnection, pUserData); } -- cgit v1.2.3