From 06f646aec4dbce64d28bae1be6111bd833f8e79e Mon Sep 17 00:00:00 2001 From: Paul Nettle Date: Fri, 25 Aug 2017 09:30:39 -0500 Subject: Initial version 1.0 --- src/HciAdapter.cpp | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 src/HciAdapter.cpp (limited to 'src/HciAdapter.cpp') diff --git a/src/HciAdapter.cpp b/src/HciAdapter.cpp new file mode 100644 index 0000000..8e906e6 --- /dev/null +++ b/src/HciAdapter.cpp @@ -0,0 +1,384 @@ +// Copyright 2017 Paul Nettle. +// +// This file is part of Gobbledegook. +// +// Gobbledegook is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Gobbledegook is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Gobbledegook. If not, see . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> INSIDE THIS FILE +// >> +// +// Protocol-level code for the Bluetooth Management API, which is used to configure the Bluetooth adapter +// +// >> +// >>> DISCUSSION +// >> +// +// This class is intended for use by `Mgmt` (see Mgmt.cpp). +// +// The Bluetooth Management API, is used to configure the Bluetooth adapter (such as enabling LE, setting the device name, etc.) +// This class uses HciSocket (HciSocket.h) for the raw communications. +// +// The information for this implementation (as well as HciSocket.h) came from: +// +// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt +// +// KNOWN LIMITATIONS: +// +// This is far from a complete implementation. I'm not even sure how reliable of an implementation this is. However, I can say with +// _complete_confidence_ that it works on my machine after numerous minutes of testing. +// +// One notable limitation is that this code doesn't work with the Bluetooth Management API in the way it was intended. The Bluetooth +// Management API treats the sending and receiving of data differently. It receives commands on the HCI socket and acts upon them. +// It also sends events on the same socket. It is important to note that there is not necessarily a 1:1 correlation from commands +// received to events generated. For example, an event can be generated when a bluetooth client disconnects, even though no command +// was sent for which that event is associated. +// +// However, for initialization, it seems to be generally safe to treat them as "nearly 1:1". The solution below is to consume all +// events and look for the event that we're waiting on. This seems to work in my environment (Raspberry Pi) fairly well, but please +// do use this with caution. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#include + +#include "HciAdapter.h" +#include "HciSocket.h" +#include "Utils.h" +#include "Logger.h" + +static const int kMinCommandCode = 0x0001; +static const int kMaxCommandCode = 0x0043; +static const char *kCommandCodeNames[kMaxCommandCode] = +{ + "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 + "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 + // 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 +}; + +static const int kMinEventType = 0x0001; +static const int kMaxEventType = 0x0025; +static const char *kEventTypeNames[kMaxEventType] = +{ + "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 +}; + +// Connects the HCI socket if a connection does not already exist +// +// If a connection already exists, this method will do nothing and return true. +// +// Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect +// +// 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()) + { + disconnect(); + return false; + } + + return true; +} + +// Returns true if connected to the HCI socket, otherwise false +// +// Note that it shouldn't be necessary to connect manually; any action requiring a connection will automatically connect +bool HciAdapter::isConnected() const +{ + return hciSocket.isConnected(); +} + +// Disconnects from the HCI Socket +// +// If the connection is not connected, this method will do nothing. +// +// It isn't necessary to disconnect manually; the HCI socket will get disocnnected automatically upon destruction +void HciAdapter::disconnect() +{ + if (isConnected()) + { + hciSocket.disconnect(); + } +} + +// Sends a command over the HCI socket +// +// If the HCI socket is not connected, it will auto-connect prior to sending the command. In the case of a failed auto-connect, +// a failure is returned. +// +// Returns true on success, otherwise false +bool HciAdapter::sendCommand(Header &request, ResponseEvent &response, int responseLen) +{ + // 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"); + + request.toNetwork(); + uint8_t *pRequest = reinterpret_cast(&request); + std::vector requestPacket = std::vector(pRequest, pRequest + sizeof(request) + request.dataSize); + if (!hciSocket.write(requestPacket)) + { + 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(SSTR << " + Not enough data for the current event"); + return false; + } + + // 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::info(SSTR << " + Received event type " << Utils::hex(pEvent->header.code) << " (" << eventTypeName << ")"); + + // Success event for our command? + if (pEvent->header.code != 1 && pEvent->commandCode != commandCode) + { + Logger::warn(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; + } + + // Return the last good event + buffer = lastGoodEvent; + return true; +} -- cgit v1.2.3