// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "GAP.hpp"
#include <log/log.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>
#include <service-bluetooth/messages/ResponseVisibleDevices.hpp>
#include <service-bluetooth/messages/Unpair.hpp>
#include <service-bluetooth/messages/Passkey.hpp>
#include <service-bluetooth/Constants.hpp>
extern "C"
{
#include "btstack.h"
#include "hci.h"
};
namespace bluetooth
{
Devicei GAP::currentlyProccesedDevice;
sys::Service *GAP::ownerService = nullptr;
std::vector<Devicei> GAP::devices;
btstack_packet_callback_registration_t GAP::cb_handler;
ScanState GAP::state;
bd_addr_t GAP::SSPaddress;
bool GAP::legacyPairing = false;
std::string GAP::SSPname{};
auto GAP::registerScan() -> Error
{
LOG_INFO("GAP register scan!");
/// -> this have to be called prior to power on!
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
cb_handler.callback = &packetHandler;
hci_add_event_handler(&cb_handler);
return Error();
}
auto GAP::scan() -> Error
{
if (hci_get_state() == HCI_STATE_WORKING) {
devices.clear();
if (auto ret = startScan(); ret != 0) {
LOG_ERROR("Start scan error!: 0x%X", ret);
return Error(Error::LibraryError, ret);
}
}
else {
return Error(Error::NotReady);
}
return Error();
}
void GAP::stopScan()
{
gap_inquiry_force_stop();
LOG_INFO("Scan stopped!");
}
void GAP::setVisibility(bool visibility)
{
gap_discoverable_control(static_cast<std::uint8_t>(visibility));
LOG_INFO("Visibility: %s", visibility ? "true" : "false");
}
auto GAP::pair(const Devicei &device, std::uint8_t protectionLevel) -> bool
{
if (hci_get_state() == HCI_STATE_WORKING) {
auto devIndex = getDeviceIndexForAddress(devices, device.address);
currentlyProccesedDevice = devices.at(devIndex);
return gap_dedicated_bonding(const_cast<uint8_t *>(device.address), protectionLevel) == 0;
}
return false;
}
auto GAP::getDeviceIndexForAddress(const std::vector<Devicei> &devs, const bd_addr_t addr) -> int
{
auto result = std::find_if(std::begin(devs), std::end(devs), [addr](const Devicei &device) {
return bd_addr_cmp(addr, device.address) == 0;
});
if (result == std::end(devs)) {
return -1;
}
return std::distance(std::begin(devs), result);
}
void GAP::sendDevices()
{
auto msg = std::make_shared<message::bluetooth::ResponseVisibleDevices>(devices);
ownerService->bus.sendMulticast(std::move(msg), sys::BusChannel::BluetoothNotifications);
}
auto GAP::startScan() -> int
{
LOG_INFO("Starting inquiry scan..");
return gap_inquiry_start(inquiryIntervalSeconds);
}
auto GAP::remoteNameToFetch() -> bool
{
auto result = std::find_if(std::begin(devices), std::end(devices), [](Devicei &device) {
return device.state == REMOTE_NAME_REQUEST;
});
return result != std::end(devices);
}
void GAP::fetchRemoteName()
{
for (auto &device : bluetooth::GAP::devices) {
if (device.state == REMOTE_NAME_REQUEST) {
device.state = REMOTE_NAME_INQUIRED;
LOG_INFO("Get remote name...");
gap_remote_name_request(device.address, device.pageScanRepetitionMode, device.clockOffset | 0x8000);
return;
}
}
}
void GAP::continueScanning()
{
if (remoteNameToFetch()) {
fetchRemoteName();
return;
}
startScan();
}
auto GAP::updateDeviceName(std::uint8_t *packet, bd_addr_t &addr) -> bool
{
reverse_bd_addr(&packet[3], addr);
auto index = getDeviceIndexForAddress(devices, addr);
if (index >= 0) {
if (packet[2] == 0) {
devices[index].state = REMOTE_NAME_FETCHED;
devices[index].name = std::string{reinterpret_cast<const char *>(&packet[9])};
return true;
}
else {
LOG_INFO("Failed to get name: page timeout");
}
}
return false;
}
void GAP::addNewDevice(std::uint8_t *packet, bd_addr_t &addr)
{
Devicei device;
device.setAddress(&addr);
device.pageScanRepetitionMode = gap_event_inquiry_result_get_page_scan_repetition_mode(packet);
device.clockOffset = gap_event_inquiry_result_get_clock_offset(packet);
device.classOfDevice = gap_event_inquiry_result_get_class_of_device(packet);
LOG_INFO("Device found ");
LOG_INFO("with COD: 0x%06x, ", static_cast<unsigned int>(device.classOfDevice));
LOG_INFO("pageScan %d, ", device.pageScanRepetitionMode);
LOG_INFO("clock offset 0x%04x", device.clockOffset);
if (gap_event_inquiry_result_get_rssi_available(packet) != 0u) {
LOG_INFO(", rssi %d dBm", static_cast<int8_t>(gap_event_inquiry_result_get_rssi(packet)));
}
if (gap_event_inquiry_result_get_name_available(packet) != 0u) {
auto name = gap_event_inquiry_result_get_name(packet);
device.name = std::string{reinterpret_cast<const char *>(name)};
device.state = REMOTE_NAME_FETCHED;
}
else {
device.state = REMOTE_NAME_REQUEST;
device.name = std::string{};
}
devices.emplace_back(std::move(device));
}
void GAP::processInquiryResult(std::uint8_t *packet, bd_addr_t &addr)
{
gap_event_inquiry_result_get_bd_addr(packet, addr);
auto index = getDeviceIndexForAddress(devices, addr);
if (index >= 0) {
return; // already in our list
}
uint32_t classOfDevice = gap_event_inquiry_result_get_class_of_device(packet);
LOG_INFO("Device CoD: %" PRIx32 "", classOfDevice);
///> Device has to support services: AUDIO for HFP and HSP profiles, and RENDERING for SNK of A2DP profile
if (!(classOfDevice & TYPE_OF_SERVICE::REMOTE_SUPPORTED_SERVICES)) {
LOG_INFO("Ignoring device with incompatible services: %s, ",
getListOfSupportedServicesInString(classOfDevice).c_str());
return;
}
addNewDevice(packet, addr);
sendDevices();
}
void GAP::processInquiryComplete()
{
for (auto &device : devices) {
// retry remote name request
if (device.state == REMOTE_NAME_INQUIRED) {
device.state = REMOTE_NAME_REQUEST;
}
}
continueScanning();
}
void GAP::processNameRequestComplete(std::uint8_t *packet, bd_addr_t &addr)
{
if (updateDeviceName(packet, addr)) {
sendDevices();
}
continueScanning();
}
void GAP::processDedicatedBondingCompleted(std::uint8_t *packet, bd_addr_t &addr)
{
auto result = packet[2];
auto msg = std::make_shared<BluetoothPairResultMessage>(currentlyProccesedDevice, result == 0u);
ownerService->bus.sendUnicast(std::move(msg), service::name::bluetooth);
}
/* @text In ACTIVE, the following events are processed:
* - GAP Inquiry result event: BTstack provides a unified inquiry result that contain
* Class of Device (CoD), page scan mode, clock offset. RSSI and name (from EIR) are optional.
* - Inquiry complete event: the remote name is requested for devices without a fetched
* name. The state of a remote name can be one of the following:
* REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, or REMOTE_NAME_FETCHED.
* - Remote name request complete event: the remote name is stored in the table and the
* state is updated to REMOTE_NAME_FETCHED. The query of remote names is continued.
*/
void GAP::activeStateHandler(std::uint8_t eventType, std::uint8_t *packet, bd_addr_t &addr)
{
switch (eventType) {
case GAP_EVENT_INQUIRY_RESULT:
processInquiryResult(packet, addr);
break;
case GAP_EVENT_INQUIRY_COMPLETE:
processInquiryComplete();
break;
case HCI_EVENT_USER_PASSKEY_REQUEST: {
hci_event_user_passkey_request_get_bd_addr(packet, SSPaddress);
gap_remote_name_request(SSPaddress, PAGE_SCAN_MODE_STANDARD, 0);
auto msg = std::make_shared<::message::bluetooth::RequestPasskey>();
ownerService->bus.sendMulticast(std::move(msg), sys::BusChannel::BluetoothNotifications);
} break;
case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:
if (legacyPairing) {
processNameRequestComplete(packet, addr);
}
else {
SSPname = reinterpret_cast<const char *>(&packet[9]);
LOG_SENSITIVE(LOGDEBUG, "Name: %s", SSPname.c_str());
Devicei newDevice(SSPname);
newDevice.setAddress(&SSPaddress);
devices.push_back(newDevice);
}
break;
case GAP_EVENT_DEDICATED_BONDING_COMPLETED:
processDedicatedBondingCompleted(packet, addr);
break;
case HCI_EVENT_SIMPLE_PAIRING_COMPLETE:
processSimplePairingCompleted(packet, addr);
break;
case GAP_EVENT_PAIRING_COMPLETE:
legacyPairing = false;
break;
default:
break;
}
}
void GAP::initStateHandler(std::uint8_t eventType, std::uint8_t *packet)
{
if (eventType == BTSTACK_EVENT_STATE) {
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
state = ScanState::active;
}
}
}
void GAP::packetHandler(std::uint8_t packet_type, std::uint16_t channel, std::uint8_t *packet, std::uint16_t size)
{
bd_addr_t addr;
if (packet_type != HCI_EVENT_PACKET) {
return;
}
if (hci_event_packet_get_type(packet) == HCI_EVENT_PIN_CODE_REQUEST) {
LOG_DEBUG("PIN code request!");
legacyPairing = true;
hci_event_pin_code_request_get_bd_addr(packet, addr);
auto msg = std::make_shared<::message::bluetooth::RequestPasskey>();
ownerService->bus.sendMulticast(std::move(msg), sys::BusChannel::BluetoothNotifications);
}
const auto eventType = hci_event_packet_get_type(packet);
switch (state) {
case ScanState::init:
initStateHandler(eventType, packet);
break;
case ScanState::active:
activeStateHandler(eventType, packet, addr);
break;
default:
break;
}
}
GAP::GAP(sys::Service *owner)
{
ownerService = owner;
state = ScanState::init;
}
auto GAP::getDevicesList() -> const std::vector<Devicei> &
{
return devices;
}
auto GAP::unpair(const Devicei &device) -> bool
{
LOG_INFO("Unpairing device");
gap_drop_link_key_for_bd_addr(const_cast<uint8_t *>(device.address));
LOG_INFO("Device unpaired");
ownerService->bus.sendMulticast(std::make_shared<message::bluetooth::UnpairResult>(device, true),
sys::BusChannel::BluetoothNotifications);
return true;
}
void GAP::respondPinCode(const std::string &pin)
{
if (legacyPairing) {
gap_pin_code_response(currentlyProccesedDevice.address, pin.c_str());
return;
}
unsigned int passkey = 0;
try {
passkey = stoi(pin);
LOG_DEBUG("Sending %06u as a passkey", passkey);
}
catch (const std::invalid_argument &e) {
LOG_ERROR("STOI error: %s", e.what());
}
gap_ssp_passkey_response(SSPaddress, passkey);
}
void GAP::processSimplePairingCompleted(std::uint8_t *packet, bd_addr_t &addr)
{
auto status = hci_event_simple_pairing_complete_get_status(packet);
auto device = devices.at(getDeviceIndexForAddress(devices, SSPaddress));
auto msg = std::make_shared<BluetoothPairResultMessage>(device, status == 0u);
ownerService->bus.sendUnicast(std::move(msg), service::name::bluetooth);
}
} // namespace bluetooth