blob: 590252ddc13f4b1762dbba8347d5bb0cc5ec51d1 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "commands.h"
#include <endian.h>
#include <lib/zx/time.h>
#include <cstring>
#include <iostream>
#include "src/connectivity/bluetooth/core/bt-host/common/advertising_data.h"
#include "src/connectivity/bluetooth/core/bt-host/common/manufacturer_names.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/advertising_report_parser.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/control_packets.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace hcitool {
namespace {
::bt::hci::CommandChannel::TransactionId SendCommand(
const CommandData* cmd_data, std::unique_ptr<::bt::hci::CommandPacket> packet,
::bt::hci::CommandChannel::CommandCallback cb, fit::closure complete_cb) {
return cmd_data->cmd_channel()->SendCommand(
std::move(packet),
[complete_cb = std::move(complete_cb), cb = std::move(cb)](
::bt::hci::CommandChannel::TransactionId id, const ::bt::hci::EventPacket& event) {
if (event.event_code() == ::bt::hci::kCommandStatusEventCode) {
auto status = event.ToStatus();
std::cout << " Command Status: " << status.ToString() << " (id=" << id << ")"
<< std::endl;
if (status) {
complete_cb();
}
return;
}
cb(id, event);
});
}
void LogCommandResult(::bt::hci::StatusCode status, ::bt::hci::CommandChannel::TransactionId id,
const std::string& event_name = "Command Complete") {
std::cout << fxl::StringPrintf(" %s - status: 0x%02x (id=%lu)\n", event_name.c_str(), status,
id);
}
::bt::hci::CommandChannel::TransactionId SendCompleteCommand(
const CommandData* cmd_data, std::unique_ptr<::bt::hci::CommandPacket> packet,
fit::closure complete_cb) {
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::SimpleReturnParams>();
LogCommandResult(return_params->status, id);
complete_cb();
};
return SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
}
// TODO(armansito): Move this to a library header as it will be useful
// elsewhere.
std::string AdvEventTypeToString(::bt::hci::LEAdvertisingEventType type) {
switch (type) {
case ::bt::hci::LEAdvertisingEventType::kAdvInd:
return "ADV_IND";
case ::bt::hci::LEAdvertisingEventType::kAdvDirectInd:
return "ADV_DIRECT_IND";
case ::bt::hci::LEAdvertisingEventType::kAdvScanInd:
return "ADV_SCAN_IND";
case ::bt::hci::LEAdvertisingEventType::kAdvNonConnInd:
return "ADV_NONCONN_IND";
case ::bt::hci::LEAdvertisingEventType::kScanRsp:
return "SCAN_RSP";
default:
break;
}
return "(unknown)";
}
// TODO(armansito): Move this to a library header as it will be useful
// elsewhere.
std::string BdAddrTypeToString(::bt::hci::LEAddressType type) {
switch (type) {
case ::bt::hci::LEAddressType::kPublic:
return "public";
case ::bt::hci::LEAddressType::kRandom:
return "random";
case ::bt::hci::LEAddressType::kPublicIdentity:
return "public-identity (resolved private)";
case ::bt::hci::LEAddressType::kRandomIdentity:
return "random-identity (resolved private)";
default:
break;
}
return "(unknown)";
}
// TODO(armansito): Move this to a library header as it will be useful
// elsewhere.
std::vector<std::string> AdvFlagsToStrings(uint8_t flags) {
std::vector<std::string> flags_list;
if (flags & ::bt::AdvFlag::kLELimitedDiscoverableMode)
flags_list.push_back("limited-discoverable");
if (flags & ::bt::AdvFlag::kLEGeneralDiscoverableMode)
flags_list.push_back("general-discoverable");
if (flags & ::bt::AdvFlag::kBREDRNotSupported)
flags_list.push_back("bredr-not-supported");
if (flags & ::bt::AdvFlag::kSimultaneousLEAndBREDRController)
flags_list.push_back("le-and-bredr-controller");
if (flags & ::bt::AdvFlag::kSimultaneousLEAndBREDRHost)
flags_list.push_back("le-and-bredr-host");
return flags_list;
}
void DisplayAdvertisingReport(const ::bt::hci::LEAdvertisingReportData& data, int8_t rssi,
const std::string& name_filter, const std::string& addr_type_filter) {
::bt::SupplementDataReader reader(::bt::BufferView(data.data, data.length_data));
// The AD fields that we'll parse out.
uint8_t flags = 0;
std::string_view short_name, complete_name;
int8_t tx_power_lvl;
bool tx_power_present = false;
::bt::DataType type;
::bt::BufferView adv_data_field;
while (reader.GetNextField(&type, &adv_data_field)) {
switch (type) {
case ::bt::DataType::kFlags:
flags = adv_data_field.data()[0];
break;
case ::bt::DataType::kCompleteLocalName:
complete_name = adv_data_field.AsString();
break;
case ::bt::DataType::kShortenedLocalName:
short_name = adv_data_field.AsString();
break;
case ::bt::DataType::kTxPowerLevel:
tx_power_present = true;
tx_power_lvl = adv_data_field.data()[0];
break;
default:
break;
}
}
// First check if this report should be filtered out by name.
if (!name_filter.empty() && complete_name.compare(name_filter) != 0 &&
short_name.compare(name_filter) != 0) {
return;
}
// Apply the address type filter.
if (!addr_type_filter.empty()) {
FX_DCHECK(addr_type_filter == "public" || addr_type_filter == "random");
if (addr_type_filter == "public" && data.address_type != ::bt::hci::LEAddressType::kPublic &&
data.address_type != ::bt::hci::LEAddressType::kPublicIdentity)
return;
if (addr_type_filter == "random" && data.address_type != ::bt::hci::LEAddressType::kRandom &&
data.address_type != ::bt::hci::LEAddressType::kRandomIdentity)
return;
}
std::cout << " LE Advertising Report:" << std::endl;
std::cout << " RSSI: " << fxl::NumberToString(rssi) << std::endl;
std::cout << " type: " << AdvEventTypeToString(data.event_type) << std::endl;
std::cout << " address type: " << BdAddrTypeToString(data.address_type) << std::endl;
std::cout << " BD_ADDR: " << data.address.ToString() << std::endl;
std::cout << " Data Length: " << fxl::NumberToString(data.length_data) << " bytes"
<< std::endl;
if (flags) {
std::cout << " Flags: [" << fxl::JoinStrings(AdvFlagsToStrings(flags), ", ") << "]"
<< std::endl;
}
if (!short_name.empty())
std::cout << " Shortened Local Name: " << short_name << std::endl;
if (!complete_name.empty())
std::cout << " Complete Local Name: " << complete_name << std::endl;
if (tx_power_present) {
std::cout << " Tx Power Level: " << fxl::NumberToString(tx_power_lvl) << std::endl;
}
}
void DisplayInquiryResult(const ::bt::hci::InquiryResult& result) {
std::cout << " Result: " << result.bd_addr.ToString() << " ("
<< result.class_of_device.ToString() << ")" << std::endl;
}
bool HandleVersionInfo(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: version-info" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto params = event.return_params<::bt::hci::ReadLocalVersionInfoReturnParams>();
LogCommandResult(params->status, id);
if (params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
std::cout << " Version Info:" << std::endl;
std::cout << " HCI Version: Core Spec " << ::bt::hci::HCIVersionToString(params->hci_version)
<< std::endl;
std::cout << " Manufacturer Name: "
<< ::bt::GetManufacturerName(le16toh(params->manufacturer_name)) << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadLocalVersionInfo);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_Local_Version_Information (id=" << id << ")" << std::endl;
return true;
}
bool HandleReset(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: reset" << std::endl;
return false;
}
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReset);
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Reset (id=" << id << ")" << std::endl;
return true;
}
bool HandleReadBDADDR(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: read-bdaddr" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::ReadBDADDRReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
std::cout << " BD_ADDR: " << return_params->bd_addr.ToString() << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadBDADDR);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_BDADDR (id=" << id << ")" << std::endl;
return true;
}
bool HandleReadLocalName(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: read-local-name" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::ReadLocalNameReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
std::cout << " Local Name: " << return_params->local_name << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadLocalName);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_Local_Name (id=" << id << ")" << std::endl;
return true;
}
bool HandleWriteLocalName(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() != 1 || cmd_line.options().size()) {
std::cout << " Usage: write-local-name <name>" << std::endl;
return false;
}
const std::string& name = cmd_line.positional_args()[0];
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kWriteLocalName, name.length() + 1);
std::strcpy((char*)packet->mutable_payload<::bt::hci::WriteLocalNameCommandParams>()->local_name,
name.c_str());
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Write_Local_Name (id=" << id << ")" << std::endl;
return true;
}
bool HandleSetEventMask(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() != 1 || cmd_line.options().size()) {
std::cout << " Usage: set-event-mask [hex]" << std::endl;
return false;
}
std::string hex = cmd_line.positional_args()[0];
if (hex.size() >= 2 && hex[0] == '0' && hex[1] == 'x')
hex = hex.substr(2);
uint64_t mask;
if (!fxl::StringToNumberWithError<uint64_t>(hex, &mask, fxl::Base::k16)) {
std::cout << " Unrecognized hex number: " << cmd_line.positional_args()[0] << std::endl;
std::cout << " Usage: set-event-mask [hex]" << std::endl;
return false;
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::SetEventMaskCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kSetEventMask, kPayloadSize);
packet->mutable_payload<::bt::hci::SetEventMaskCommandParams>()->event_mask = htole64(mask);
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Set_Event_Mask(" << fxl::NumberToString(mask, fxl::Base::k16)
<< ") (id=" << id << ")" << std::endl;
return true;
}
bool HandleLESetAdvEnable(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() != 1 || cmd_line.options().size()) {
std::cout << " Usage: set-adv-enable [enable|disable]" << std::endl;
return false;
}
::bt::hci::GenericEnableParam value;
std::string cmd_arg = cmd_line.positional_args()[0];
if (cmd_arg == "enable") {
value = ::bt::hci::GenericEnableParam::kEnable;
} else if (cmd_arg == "disable") {
value = ::bt::hci::GenericEnableParam::kDisable;
} else {
std::cout << " Unrecognized parameter: " << cmd_arg << std::endl;
std::cout << " Usage: set-adv-enable [enable|disable]" << std::endl;
return false;
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::LESetAdvertisingEnableCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetAdvertisingEnable, kPayloadSize);
packet->mutable_payload<::bt::hci::LESetAdvertisingEnableCommandParams>()->advertising_enable =
value;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_LE_Set_Advertising_Enable (id=" << id << ")" << std::endl;
return true;
}
bool HandleLESetAdvParams(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: set-adv-params [--help|--type]" << std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options: \n"
" --help - Display this help message\n"
" --type=<type> - The advertising type. Possible values are:\n"
" - nonconn: non-connectable undirected (default)\n"
" - adv-ind: connectable and scannable undirected\n"
" - direct-low: connectable directed low-duty\n"
" - direct-high: connectable directed high-duty\n"
" - scan: scannable undirected";
std::cout << std::endl;
return false;
}
::bt::hci::LEAdvertisingType adv_type = ::bt::hci::LEAdvertisingType::kAdvNonConnInd;
std::string type;
if (cmd_line.GetOptionValue("type", &type)) {
if (type == "adv-ind") {
adv_type = ::bt::hci::LEAdvertisingType::kAdvInd;
} else if (type == "direct-low") {
adv_type = ::bt::hci::LEAdvertisingType::kAdvDirectIndLowDutyCycle;
} else if (type == "direct-high") {
adv_type = ::bt::hci::LEAdvertisingType::kAdvDirectIndHighDutyCycle;
} else if (type == "scan") {
adv_type = ::bt::hci::LEAdvertisingType::kAdvScanInd;
} else if (type == "nonconn") {
adv_type = ::bt::hci::LEAdvertisingType::kAdvNonConnInd;
} else {
std::cout << " Unrecognized advertising type: " << type << std::endl;
return false;
}
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::LESetAdvertisingParametersCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetAdvertisingParameters, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::LESetAdvertisingParametersCommandParams>();
params->adv_interval_min = htole16(::bt::hci::kLEAdvertisingIntervalDefault);
params->adv_interval_max = htole16(::bt::hci::kLEAdvertisingIntervalDefault);
params->adv_type = adv_type;
params->own_address_type = ::bt::hci::LEOwnAddressType::kPublic;
params->peer_address_type = ::bt::hci::LEPeerAddressType::kPublic;
params->peer_address.SetToZero();
params->adv_channel_map = ::bt::hci::kLEAdvertisingChannelAll;
params->adv_filter_policy = ::bt::hci::LEAdvFilterPolicy::kAllowAll;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_LE_Set_Advertising_Parameters (id=" << id << ")" << std::endl;
return true;
}
bool HandleLESetAdvData(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: set-adv-data [--help|--name]" << std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options: \n"
" --help - Display this help message\n"
" --name=<local-name> - Set the \"Complete Local Name\" field";
std::cout << std::endl;
return false;
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::LESetAdvertisingDataCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetAdvertisingData, kPayloadSize);
packet->mutable_view()->mutable_payload_data().SetToZeros();
std::string name;
if (cmd_line.GetOptionValue("name", &name)) {
// Each advertising data structure consists of a 1 octet length field, 1
// octet type field.
size_t adv_data_len = 2 + name.length();
if (adv_data_len > ::bt::hci::kMaxLEAdvertisingDataLength) {
std::cout << " Given name is too long" << std::endl;
return false;
}
auto params = packet->mutable_payload<::bt::hci::LESetAdvertisingDataCommandParams>();
params->adv_data_length = adv_data_len;
params->adv_data[0] = adv_data_len - 1;
params->adv_data[1] = 0x09; // Complete Local Name
std::strncpy((char*)params->adv_data + 2, name.c_str(), name.length());
} else {
packet->mutable_payload<::bt::hci::LESetAdvertisingDataCommandParams>()->adv_data_length = 0;
}
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_LE_Set_Advertising_Data (id=" << id << ")" << std::endl;
return true;
}
bool HandleLESetScanParams(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: set-scan-params [--help|--type]" << std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options: \n"
" --help - Display this help message\n"
" --type=<type> - The scan type. Possible values are:\n"
" - passive: passive scanning (default)\n"
" - active: active scanning; sends scan requests";
std::cout << std::endl;
return false;
}
::bt::hci::LEScanType scan_type = ::bt::hci::LEScanType::kPassive;
std::string type;
if (cmd_line.GetOptionValue("type", &type)) {
if (type == "passive") {
scan_type = ::bt::hci::LEScanType::kPassive;
} else if (type == "active") {
scan_type = ::bt::hci::LEScanType::kActive;
} else {
std::cout << " Unrecognized scan type: " << type << std::endl;
return false;
}
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::LESetScanParametersCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetScanParameters, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::LESetScanParametersCommandParams>();
params->scan_type = scan_type;
params->scan_interval = htole16(::bt::hci::kLEScanIntervalDefault);
params->scan_window = htole16(::bt::hci::kLEScanIntervalDefault);
params->own_address_type = ::bt::hci::LEOwnAddressType::kPublic;
params->filter_policy = ::bt::hci::LEScanFilterPolicy::kNoWhiteList;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_LE_Set_Scan_Parameters (id=" << id << ")" << std::endl;
return true;
}
bool HandleLEScan(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: set-scan-params "
"[--help|--timeout=<t>|--no-dedup|--name-filter]"
<< std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options: \n"
" --help - Display this help message\n"
" --timeout=<t> - Duration (in seconds) during which to scan\n"
" (default is 10 seconds)\n"
" --no-dedup - Tell the controller not to filter duplicate\n"
" reports\n"
" --name-filter=<prefix> - Filter advertising reports by local\n"
" name, if present.\n"
" --addr-type-filter=[public|random]";
std::cout << std::endl;
return false;
}
auto timeout = zx::sec(10); // Default to 10 seconds.
std::string timeout_str;
if (cmd_line.GetOptionValue("timeout", &timeout_str)) {
uint32_t time_seconds;
if (!fxl::StringToNumberWithError(timeout_str, &time_seconds)) {
std::cout << " Malformed timeout value: " << timeout_str << std::endl;
return false;
}
timeout = zx::sec(time_seconds);
}
std::string name_filter;
cmd_line.GetOptionValue("name-filter", &name_filter);
std::string addr_type_filter;
cmd_line.GetOptionValue("addr-type-filter", &addr_type_filter);
if (!addr_type_filter.empty() && addr_type_filter != "public" && addr_type_filter != "random") {
std::cout << " Unknown address type filter: " << addr_type_filter << std::endl;
return false;
}
::bt::hci::GenericEnableParam filter_duplicates = ::bt::hci::GenericEnableParam::kEnable;
if (cmd_line.HasOption("no-dedup")) {
filter_duplicates = ::bt::hci::GenericEnableParam::kDisable;
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::LESetScanEnableCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetScanEnable, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::LESetScanEnableCommandParams>();
params->scanning_enabled = ::bt::hci::GenericEnableParam::kEnable;
params->filter_duplicates = filter_duplicates;
// Event handler to log when we receive advertising reports
auto le_adv_report_cb = [name_filter, addr_type_filter](const ::bt::hci::EventPacket& event) {
FX_DCHECK(event.event_code() == ::bt::hci::kLEMetaEventCode);
FX_DCHECK(event.params<::bt::hci::LEMetaEventParams>().subevent_code ==
::bt::hci::kLEAdvertisingReportSubeventCode);
::bt::hci::AdvertisingReportParser parser(event);
const ::bt::hci::LEAdvertisingReportData* data;
int8_t rssi;
while (parser.GetNextReport(&data, &rssi)) {
DisplayAdvertisingReport(*data, rssi, name_filter, addr_type_filter);
}
return ::bt::hci::CommandChannel::EventCallbackResult::kContinue;
};
auto event_handler_id = cmd_data->cmd_channel()->AddLEMetaEventHandler(
::bt::hci::kLEAdvertisingReportSubeventCode, le_adv_report_cb);
fit::closure cleanup_cb = [complete_cb = complete_cb.share(), event_handler_id,
cmd_channel = cmd_data->cmd_channel()] {
cmd_channel->RemoveEventHandler(event_handler_id);
complete_cb();
};
// The callback invoked after scanning is stopped.
auto final_cb = [cleanup_cb = cleanup_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::SimpleReturnParams>();
LogCommandResult(return_params->status, id);
cleanup_cb();
};
// Delayed task that stops scanning.
auto scan_disable_cb = [cleanup_cb = cleanup_cb.share(), final_cb = std::move(final_cb),
cmd_data]() mutable {
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kLESetScanEnable, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::LESetScanEnableCommandParams>();
params->scanning_enabled = ::bt::hci::GenericEnableParam::kDisable;
params->filter_duplicates = ::bt::hci::GenericEnableParam::kDisable;
auto id = SendCommand(cmd_data, std::move(packet), std::move(final_cb), std::move(cleanup_cb));
std::cout << " Sent HCI_LE_Set_Scan_Enable (disabled) (id=" << id << ")" << std::endl;
};
auto cb = [scan_disable_cb = std::move(scan_disable_cb), cleanup_cb = cleanup_cb.share(), timeout,
dispatcher = cmd_data->dispatcher()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) mutable {
auto return_params = event.return_params<::bt::hci::SimpleReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
cleanup_cb();
return;
}
async::PostDelayedTask(dispatcher, std::move(scan_disable_cb), timeout);
};
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_LE_Set_Scan_Enable (enabled) (id=" << id << ")" << std::endl;
return true;
}
bool HandleBRScan(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: scan "
"[--help|--timeout=<t>|--filter=<prefix>|--max-responses=<n>]"
<< std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options: \n"
" --help - Display this help message\n"
" --timeout=<t> - Maximum duration (in seconds) of the scan\n"
" (default is 30 seconds)\n"
" --filter=<prefix> - Filter devices reported by name or\n"
" BR_ADDR prefix.\n"
" --max-responses=<n> - End scan after n responses are\n"
" received.\n";
std::cout << std::endl;
return false;
}
auto timeout = zx::sec(30); // Default 30 seconds.
std::string timeout_str;
if (cmd_line.GetOptionValue("timeout", &timeout_str)) {
uint32_t time_seconds;
if (!fxl::StringToNumberWithError(timeout_str, &time_seconds)) {
std::cout << " Malformed timeout value: " << timeout_str << std::endl;
return false;
}
// TODO(jamuraa): support longer than 61 second scans by repeating the
// Inquiry
if (time_seconds > 61) {
std::cout << " Maximum inquiry length is 61 seconds." << std::endl;
return false;
}
timeout = zx::sec(time_seconds);
}
std::string filter;
cmd_line.GetOptionValue("filter", &filter);
uint8_t max_responses = 0;
std::string max_responses_str;
if (cmd_line.GetOptionValue("max-responses", &max_responses_str)) {
uint32_t responses;
if (!fxl::StringToNumberWithError(max_responses_str, &responses)) {
std::cout << " Malformed maximum responses value: " << max_responses_str << std::endl;
return false;
}
if (responses > 255) {
std::cout << " Maximum responses must be less than 255." << std::endl;
return false;
}
max_responses = uint8_t(responses);
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::InquiryCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kInquiry, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::InquiryCommandParams>();
params->lap = ::bt::hci::kGIAC;
// Always use the maximum inquiry length, we will time it more accurately.
params->inquiry_length = ::bt::hci::kInquiryLengthMax;
params->num_responses = max_responses;
auto event_handler_ids = std::make_shared<std::vector<bt::hci::CommandChannel::EventHandlerId>>();
fit::closure cleanup_cb = [complete_cb = std::move(complete_cb), event_handler_ids,
cmd_channel = cmd_data->cmd_channel()] {
for (const auto& handler_id : *event_handler_ids) {
cmd_channel->RemoveEventHandler(handler_id);
}
complete_cb();
};
// Event handler to log when we receive advertising reports
auto inquiry_result_cb = [filter](const ::bt::hci::EventPacket& event) {
FX_DCHECK(event.event_code() == ::bt::hci::kInquiryResultEventCode);
const auto& result = event.params<::bt::hci::InquiryResultEventParams>();
for (int i = 0; i < result.num_responses; i++) {
if (!filter.empty() &&
!filter.compare(0, filter.length(), result.responses[i].bd_addr.ToString())) {
continue;
}
DisplayInquiryResult(result.responses[i]);
}
return ::bt::hci::CommandChannel::EventCallbackResult::kContinue;
};
event_handler_ids->push_back(cmd_data->cmd_channel()->AddEventHandler(
::bt::hci::kInquiryResultEventCode, std::move(inquiry_result_cb)));
// The callback invoked for an Inquiry Complete response.
auto inquiry_complete_cb = [cleanup_cb =
cleanup_cb.share()](const ::bt::hci::EventPacket& event) mutable {
auto params = event.params<::bt::hci::InquiryCompleteEventParams>();
std::cout << fxl::StringPrintf(" Inquiry Complete - status: 0x%02x\n", params.status);
cleanup_cb();
return ::bt::hci::CommandChannel::EventCallbackResult::kContinue;
};
event_handler_ids->push_back(cmd_data->cmd_channel()->AddEventHandler(
::bt::hci::kInquiryCompleteEventCode, std::move(inquiry_complete_cb)));
// Delayed task that stops scanning.
auto inquiry_cancel_cb = [cleanup_cb = cleanup_cb.share(), cmd_data]() mutable {
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kInquiryCancel, 0);
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(cleanup_cb));
std::cout << " Sent HCI_Inquiry_Cancel (id=" << id << ")" << std::endl;
};
auto cb = [inquiry_cancel_cb = std::move(inquiry_cancel_cb), cleanup_cb = cleanup_cb.share(),
timeout,
dispatcher = cmd_data->dispatcher()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) mutable {
auto return_params = event.params<::bt::hci::CommandStatusEventParams>();
LogCommandResult(return_params.status, id, "Command Status");
if (return_params.status != ::bt::hci::StatusCode::kSuccess) {
cleanup_cb();
return;
}
async::PostDelayedTask(dispatcher, std::move(inquiry_cancel_cb), timeout);
};
// Inquiry sends a Command Status, and then we wait for the Inquiry Complete,
// or the timer to run out, for a long time. Count this as "complete" when the
// Status comes in.
auto id = cmd_data->cmd_channel()->SendCommand(std::move(packet), std::move(cb),
::bt::hci::kCommandStatusEventCode);
std::cout << " Sent HCI_Inquiry (id=" << id << ")" << std::endl;
return true;
}
bool HandleWritePageScanActivity(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: write-page-scan-activity [--help\n"
" |--interval=<interval>\n"
" |--window=<window>]"
<< std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options:\n"
" --help - Display this help message\n"
" --mode=R0|R1|R2 - Use a specific scanning mode\n"
" --interval=<interval> - Set page scan interval (in hex)\n"
" --window=<window> - Set page scan window (in hex)\n";
std::cout << std::endl;
return false;
}
uint16_t page_scan_interval = ::bt::hci::kPageScanR1Interval;
uint16_t page_scan_window = ::bt::hci::kPageScanR1Window;
std::string mode_str;
if (cmd_line.GetOptionValue("mode", &mode_str)) {
if (mode_str == "R0") {
page_scan_interval = ::bt::hci::kPageScanR0Interval;
page_scan_window = ::bt::hci::kPageScanR0Window;
} else if (mode_str == "R1") {
page_scan_interval = ::bt::hci::kPageScanR1Interval;
page_scan_window = ::bt::hci::kPageScanR1Window;
} else if (mode_str == "R2") {
page_scan_interval = ::bt::hci::kPageScanR2Interval;
page_scan_window = ::bt::hci::kPageScanR2Window;
} else {
std::cout << " Unrecognized mode value: " << mode_str << std::endl;
return false;
}
}
// Check for manual settings.
std::string interval_str;
if (cmd_line.GetOptionValue("interval", &interval_str)) {
uint16_t parsed_interval;
if (!fxl::StringToNumberWithError(interval_str, &parsed_interval, fxl::Base::k16)) {
std::cout << " Malformed interval value: " << interval_str << std::endl;
return false;
}
if (parsed_interval < ::bt::hci::kPageScanIntervalMin ||
parsed_interval > ::bt::hci::kPageScanIntervalMax) {
std::cout << " Interval value is out of the allowed range." << std::endl;
return false;
}
if (parsed_interval % 2 != 0) {
std::cout << " Interval value must be even." << std::endl;
return false;
}
page_scan_interval = parsed_interval;
}
std::string window_str;
if (cmd_line.GetOptionValue("window", &window_str)) {
uint16_t parsed_window;
if (!fxl::StringToNumberWithError(window_str, &parsed_window, fxl::Base::k16)) {
std::cout << " Malformed window value: " << window_str << std::endl;
return false;
}
if (parsed_window < ::bt::hci::kPageScanWindowMin ||
parsed_window > ::bt::hci::kPageScanWindowMax) {
std::cout << " Window value is out of the allowed range." << std::endl;
return false;
}
if (parsed_window > page_scan_interval) {
std::cout << " Window value must be less than or equal to interval value." << std::endl;
return false;
}
page_scan_window = parsed_window;
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::WritePageScanActivityCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kWritePageScanActivity, kPayloadSize);
auto params = packet->mutable_payload<::bt::hci::WritePageScanActivityCommandParams>();
params->page_scan_interval = page_scan_interval;
params->page_scan_window = page_scan_window;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Write_Page_Scan_Activity (id=" << id << ")" << std::endl;
return true;
}
bool HandleReadPageScanActivity(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: read-page-scan-activity" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::ReadPageScanActivityReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
std::cout << " Interval: " << return_params->page_scan_interval << std::endl;
std::cout << " Window: " << return_params->page_scan_window << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadPageScanActivity);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_Page_Scan_Activity (id=" << id << ")" << std::endl;
return true;
}
bool HandleWritePageScanType(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size()) {
std::cout << " Usage: write-page-scan-type [--help|--standard|--interlaced]" << std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Options:\n"
" --help - Display this help message\n"
" --type=standard|interlaced - Choose scanning type";
std::cout << std::endl;
return false;
}
::bt::hci::PageScanType page_scan_type = ::bt::hci::PageScanType::kStandardScan;
std::string type_str;
if (cmd_line.GetOptionValue("type", &type_str)) {
if (type_str == "standard") {
page_scan_type = ::bt::hci::PageScanType::kStandardScan;
} else if (type_str == "interlaced") {
page_scan_type = ::bt::hci::PageScanType::kInterlacedScan;
} else {
std::cout << " Unrecognized type: " << type_str << std::endl;
}
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::WritePageScanTypeCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kWritePageScanType, kPayloadSize);
packet->mutable_payload<::bt::hci::WritePageScanTypeCommandParams>()->page_scan_type =
page_scan_type;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Write_Page_Scan_Type (id=" << id << ")" << std::endl;
return true;
}
bool HandleReadPageScanType(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: read-page-scan-type" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::ReadPageScanTypeReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
if (return_params->page_scan_type == ::bt::hci::PageScanType::kStandardScan) {
std::cout << " Type: standard" << std::endl;
} else if (return_params->page_scan_type == ::bt::hci::PageScanType::kInterlacedScan) {
std::cout << " Type: interlaced" << std::endl;
} else {
std::cout << " Type: unknown" << std::endl;
}
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadPageScanType);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_Page_Scan_Type (id=" << id << ")" << std::endl;
return true;
}
bool HandleWriteScanEnable(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() > 2) {
std::cout << " Usage: write-scan-enable [--help] [page] [inquiry]" << std::endl;
return false;
}
if (cmd_line.HasOption("help")) {
std::cout << " Arguments:\n"
" include \"page\" to enable page scan\n"
" include \"inquiry\" to enable inquiry scan\n"
" Options:\n"
" --help - Display this help message";
std::cout << std::endl;
return false;
}
::bt::hci::ScanEnableType scan_enable = 0x00;
for (std::string positional_arg : cmd_line.positional_args()) {
if (positional_arg == "inquiry") {
scan_enable |= (::bt::hci::ScanEnableType)::bt::hci::ScanEnableBit::kInquiry;
} else if (positional_arg == "page") {
scan_enable |= (::bt::hci::ScanEnableType)::bt::hci::ScanEnableBit::kPage;
} else {
std::cout << " Unrecognized positional argument: " << positional_arg << std::endl;
return false;
}
}
constexpr size_t kPayloadSize = sizeof(::bt::hci::WriteScanEnableCommandParams);
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kWriteScanEnable, kPayloadSize);
packet->mutable_payload<::bt::hci::WriteScanEnableCommandParams>()->scan_enable = scan_enable;
auto id = SendCompleteCommand(cmd_data, std::move(packet), std::move(complete_cb));
std::cout << " Sent HCI_Write_Scan_Enable (id=" << id << ")" << std::endl;
return true;
}
bool HandleReadScanEnable(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (cmd_line.positional_args().size() || cmd_line.options().size()) {
std::cout << " Usage: read-scan-enable" << std::endl;
return false;
}
auto cb = [complete_cb = complete_cb.share()](::bt::hci::CommandChannel::TransactionId id,
const ::bt::hci::EventPacket& event) {
auto return_params = event.return_params<::bt::hci::ReadScanEnableReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::bt::hci::StatusCode::kSuccess) {
complete_cb();
return;
}
if (return_params->scan_enable &
(::bt::hci::ScanEnableType)::bt::hci::ScanEnableBit::kInquiry) {
std::cout << " Inquiry scan: enabled" << std::endl;
} else {
std::cout << " Inquiry scan: disabled" << std::endl;
}
if (return_params->scan_enable & (::bt::hci::ScanEnableType)::bt::hci::ScanEnableBit::kPage) {
std::cout << " Page scan: enabled" << std::endl;
} else {
std::cout << " Page scan: disabled" << std::endl;
}
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci::kReadScanEnable);
auto id = SendCommand(cmd_data, std::move(packet), std::move(cb), std::move(complete_cb));
std::cout << " Sent HCI_Read_Scan_Enable (id=" << id << ")" << std::endl;
return true;
}
} // namespace
void RegisterCommands(const CommandData* cmd_data,
::bluetooth_tools::CommandDispatcher* dispatcher) {
FX_DCHECK(dispatcher);
#define BIND(handler) std::bind(&handler, cmd_data, std::placeholders::_1, std::placeholders::_2)
dispatcher->RegisterHandler("version-info", "Send HCI_Read_Local_Version_Information",
BIND(HandleVersionInfo));
dispatcher->RegisterHandler("reset", "Send HCI_Reset", BIND(HandleReset));
dispatcher->RegisterHandler("read-bdaddr", "Send HCI_Read_BDADDR", BIND(HandleReadBDADDR));
dispatcher->RegisterHandler("read-local-name", "Send HCI_Read_Local_Name",
BIND(HandleReadLocalName));
dispatcher->RegisterHandler("write-local-name", "Send HCI_Write_Local_Name",
BIND(HandleWriteLocalName));
dispatcher->RegisterHandler("set-event-mask", "Send HCI_Set_Event_Mask",
BIND(HandleSetEventMask));
dispatcher->RegisterHandler("le-set-adv-enable", "Send HCI_LE_Set_Advertising_Enable",
BIND(HandleLESetAdvEnable));
dispatcher->RegisterHandler("le-set-adv-params", "Send HCI_LE_Set_Advertising_Parameters",
BIND(HandleLESetAdvParams));
dispatcher->RegisterHandler("le-set-adv-data", "Send HCI_LE_Set_Advertising_Data",
BIND(HandleLESetAdvData));
dispatcher->RegisterHandler("le-set-scan-params", "Send HCI_LE_Set_Scan_Parameters",
BIND(HandleLESetScanParams));
dispatcher->RegisterHandler("le-scan", "Perform a LE device scan for a limited duration",
BIND(HandleLEScan));
dispatcher->RegisterHandler("scan", "Perform a device scan for a limited duration",
BIND(HandleBRScan));
dispatcher->RegisterHandler("write-page-scan-activity", "Send HCI_Write_Page_Scan_Activity",
BIND(HandleWritePageScanActivity));
dispatcher->RegisterHandler("read-page-scan-activity", "Send HCI_Read_Page_Scan_Activity",
BIND(HandleReadPageScanActivity));
dispatcher->RegisterHandler("write-page-scan-type", "Send HCI_Write_Page_Scan_Type",
BIND(HandleWritePageScanType));
dispatcher->RegisterHandler("read-page-scan-type", "Send HCI_Read_Page_Scan_Type",
BIND(HandleReadPageScanType));
dispatcher->RegisterHandler("write-scan-enable", "Send HCI_Write_Scan_Enable",
BIND(HandleWriteScanEnable));
dispatcher->RegisterHandler("read-scan-enable", "Send HCI_Read_Scan_Enable",
BIND(HandleReadScanEnable));
#undef BIND
}
} // namespace hcitool