// 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;
}