blob: 1bd36fbca1d41d46438ce84454cc651ae7c88c5b [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/async/cpp/task.h>
#include <lib/zx/time.h>
#include <cstdint>
#include <cstring>
#include <iostream>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/manufacturer_names.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/advertising_report_parser.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/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 {
template <typename T>
::bt::hci::CommandChannel::TransactionId SendCommand(const CommandData* cmd_data, T 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_spec::kCommandStatusEventCode) {
auto status = event.ToResult();
std::cout << " Command Status: " << bt_str(status) << " (id=" << id << ")" << std::endl;
if (status.is_ok()) {
complete_cb();
}
return;
}
cb(id, event);
});
}
void LogCommandResult(pw::bluetooth::emboss::StatusCode status,
::bt::hci::CommandChannel::TransactionId id,
const std::string& event_name = "Command Complete") {
std::cout << fxl::StringPrintf(" %s - status: 0x%02hhx (id=%lu)\n", event_name.c_str(),
static_cast<unsigned char>(status), id);
}
template <typename T>
::bt::hci::CommandChannel::TransactionId SendCompleteCommand(const CommandData* cmd_data, T 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_spec::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_spec::LEAdvertisingEventType type) {
switch (type) {
case ::bt::hci_spec::LEAdvertisingEventType::kAdvInd:
return "ADV_IND";
case ::bt::hci_spec::LEAdvertisingEventType::kAdvDirectInd:
return "ADV_DIRECT_IND";
case ::bt::hci_spec::LEAdvertisingEventType::kAdvScanInd:
return "ADV_SCAN_IND";
case ::bt::hci_spec::LEAdvertisingEventType::kAdvNonConnInd:
return "ADV_NONCONN_IND";
case ::bt::hci_spec::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_spec::LEAddressType type) {
switch (type) {
case ::bt::hci_spec::LEAddressType::kPublic:
return "public";
case ::bt::hci_spec::LEAddressType::kRandom:
return "random";
case ::bt::hci_spec::LEAddressType::kPublicIdentity:
return "public-identity (resolved private)";
case ::bt::hci_spec::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_spec::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()) {
BT_ASSERT(addr_type_filter == "public" || addr_type_filter == "random");
if (addr_type_filter == "public" && data.address_type != bt::hci_spec::LEAddressType::kPublic &&
data.address_type != ::bt::hci_spec::LEAddressType::kPublicIdentity)
return;
if (addr_type_filter == "random" &&
data.address_type != ::bt::hci_spec::LEAddressType::kRandom &&
data.address_type != ::bt::hci_spec::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 pw::bluetooth::emboss::InquiryResultView& view) {
std::cout << " Result: " << bt::DeviceAddressBytes{view.bd_addr()}.ToString() << " ("
<< bt::DeviceClass{static_cast<unsigned char>(
view.class_of_device().BackingStorage().ReadUInt())}
.ToString()
<< ")\n";
}
bool HandleVersionInfo(const CommandData* cmd_data, const fxl::CommandLine& cmd_line,
fit::closure complete_cb) {
if (!cmd_line.positional_args().empty() || !cmd_line.options().empty()) {
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_spec::ReadLocalVersionInfoReturnParams>();
LogCommandResult(params->status, id);
if (params->status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
complete_cb();
return;
}
std::cout << " Version Info:" << std::endl;
std::cout << " HCI Version: Core Spec "
<< bt::hci_spec::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_spec::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().empty() || !cmd_line.options().empty()) {
std::cout << " Usage: reset" << std::endl;
return false;
}
auto packet = ::bt::hci::CommandPacket::New(::bt::hci_spec::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().empty() || !cmd_line.options().empty()) {
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_spec::ReadBDADDRReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
complete_cb();
return;
}
std::cout << " BD_ADDR: " << return_params->bd_addr.ToString() << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci_spec::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().empty() || !cmd_line.options().empty()) {
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_spec::ReadLocalNameReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
complete_cb();
return;
}
std::cout << " Local Name: " << return_params->local_name << std::endl;
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci_spec::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().empty()) {
std::cout << " Usage: write-local-name <name>" << std::endl;
return false;
}
const std::string& name = cmd_line.positional_args()[0];
auto write_name =
::bt::hci::EmbossCommandPacket::New<::pw::bluetooth::emboss::WriteLocalNameCommandWriter>(
::bt::hci_spec::kWriteLocalName);
auto write_name_view = write_name.view_t();
auto local_name = write_name_view.local_name().BackingStorage();
size_t name_size = std::min(name.size(), ::bt::hci_spec::kMaxNameLength);
// Use ContiguousBuffer instead of constructing LocalName view in case of invalid view being
// created when name is not large enough for the view
auto name_buf = emboss::support::ReadOnlyContiguousBuffer(&name);
local_name.CopyFrom(name_buf, name_size);
auto id = SendCompleteCommand(cmd_data, std::move(write_name), 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().empty()) {
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;
}
auto set_event =
::bt::hci::EmbossCommandPacket::New<::pw::bluetooth::emboss::SetEventMaskCommandWriter>(
::bt::hci_spec::kSetEventMask);
auto set_event_params = set_event.view_t();
set_event_params.event_mask().Write(mask);
auto id = SendCompleteCommand(cmd_data, std::move(set_event), 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().empty()) {
std::cout << " Usage: set-adv-enable [enable|disable]" << std::endl;
return false;
}
::pw::bluetooth::emboss::GenericEnableParam value;
std::string cmd_arg = cmd_line.positional_args()[0];
if (cmd_arg == "enable") {
value = ::pw::bluetooth::emboss::GenericEnableParam::ENABLE;
} else if (cmd_arg == "disable") {
value = ::pw::bluetooth::emboss::GenericEnableParam::DISABLE;
} else {
std::cout << " Unrecognized parameter: " << cmd_arg << std::endl;
std::cout << " Usage: set-adv-enable [enable|disable]" << std::endl;
return false;
}
auto packet = ::bt::hci::EmbossCommandPacket::New<
::pw::bluetooth::emboss::LESetAdvertisingEnableCommandWriter>(
::bt::hci_spec::kLESetAdvertisingEnable);
auto packet_view = packet.view_t();
packet_view.advertising_enable().Write(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().empty()) {
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;
}
::pw::bluetooth::emboss::LEAdvertisingType adv_type =
::pw::bluetooth::emboss::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED;
std::string type;
if (cmd_line.GetOptionValue("type", &type)) {
if (type == "adv-ind") {
adv_type = ::pw::bluetooth::emboss::LEAdvertisingType::CONNECTABLE_AND_SCANNABLE_UNDIRECTED;
} else if (type == "direct-low") {
adv_type = ::pw::bluetooth::emboss::LEAdvertisingType::CONNECTABLE_LOW_DUTY_CYCLE_DIRECTED;
} else if (type == "direct-high") {
adv_type = ::pw::bluetooth::emboss::LEAdvertisingType::CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED;
} else if (type == "scan") {
adv_type = ::pw::bluetooth::emboss::LEAdvertisingType::SCANNABLE_UNDIRECTED;
} else if (type == "nonconn") {
adv_type = ::pw::bluetooth::emboss::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED;
} else {
std::cout << " Unrecognized advertising type: " << type << std::endl;
return false;
}
}
auto packet = ::bt::hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingParametersCommandWriter>(
::bt::hci_spec::kLESetAdvertisingParameters);
auto params = packet.view_t();
params.advertising_interval_min().UncheckedWrite(::bt::hci_spec::kLEAdvertisingIntervalDefault);
params.advertising_interval_max().UncheckedWrite(::bt::hci_spec::kLEAdvertisingIntervalDefault);
params.adv_type().Write(adv_type);
params.own_address_type().Write(pw::bluetooth::emboss::LEOwnAddressType::PUBLIC);
params.peer_address_type().Write(pw::bluetooth::emboss::LEPeerAddressType::PUBLIC);
params.advertising_channel_map().BackingStorage().WriteUInt(
::bt::hci_spec::kLEAdvertisingChannelAll);
params.advertising_filter_policy().Write(
pw::bluetooth::emboss::LEAdvertisingFilterPolicy::ALLOW_ALL);
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().empty()) {
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;
}
auto packet =
::bt::hci::EmbossCommandPacket::New<pw::bluetooth::emboss::LESetAdvertisingDataCommandWriter>(
::bt::hci_spec::kLESetAdvertisingData);
auto params = packet.view_t();
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_spec::kMaxLEAdvertisingDataLength) {
std::cout << " Given name is too long" << std::endl;
return false;
}
params.advertising_data_length().Write(static_cast<uint8_t>(adv_data_len));
unsigned char* data = params.advertising_data().BackingStorage().data();
data[0] = static_cast<uint8_t>(adv_data_len - 1); // Length
data[1] = 0x09; // Type: Complete Local Name
std::strncpy((char*)data + 2, name.c_str(), name.length());
}
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;
}
pw::bluetooth::emboss::LEScanType scan_type = pw::bluetooth::emboss::LEScanType::PASSIVE;
std::string type;
if (cmd_line.GetOptionValue("type", &type)) {
if (type == "passive") {
scan_type = pw::bluetooth::emboss::LEScanType::PASSIVE;
} else if (type == "active") {
scan_type = pw::bluetooth::emboss::LEScanType::ACTIVE;
} else {
std::cout << " Unrecognized scan type: " << type << std::endl;
return false;
}
}
auto packet =
::bt::hci::EmbossCommandPacket::New<pw::bluetooth::emboss::LESetScanParametersCommandWriter>(
::bt::hci_spec::kLESetScanParameters);
auto params = packet.view_t();
params.le_scan_type().Write(scan_type);
params.le_scan_interval().Write(::bt::hci_spec::kLEScanIntervalDefault);
params.le_scan_window().Write(::bt::hci_spec::kLEScanIntervalDefault);
params.own_address_type().Write(pw::bluetooth::emboss::LEOwnAddressType::PUBLIC);
params.scanning_filter_policy().Write(
pw::bluetooth::emboss::LEScanFilterPolicy::BASIC_UNFILTERED);
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().empty()) {
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;
}
::pw::bluetooth::emboss::GenericEnableParam filter_duplicates =
::pw::bluetooth::emboss::GenericEnableParam::ENABLE;
if (cmd_line.HasOption("no-dedup")) {
filter_duplicates = ::pw::bluetooth::emboss::GenericEnableParam::DISABLE;
}
auto packet =
::bt::hci::EmbossCommandPacket::New<pw::bluetooth::emboss::LESetScanEnableCommandWriter>(
::bt::hci_spec::kLESetScanEnable);
auto params = packet.view_t();
params.le_scan_enable().Write(pw::bluetooth::emboss::GenericEnableParam::ENABLE);
params.filter_duplicates().Write(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) {
BT_ASSERT(event.event_code() == ::bt::hci_spec::kLEMetaEventCode);
BT_ASSERT(event.params<::bt::hci_spec::LEMetaEventParams>().subevent_code ==
::bt::hci_spec::kLEAdvertisingReportSubeventCode);
::bt::hci::AdvertisingReportParser parser(event);
const ::bt::hci_spec::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_spec::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_spec::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::EmbossCommandPacket::New<pw::bluetooth::emboss::LESetScanEnableCommandWriter>(
::bt::hci_spec::kLESetScanEnable);
auto enable_params = packet.view_t();
enable_params.le_scan_enable().Write(pw::bluetooth::emboss::GenericEnableParam::DISABLE);
enable_params.filter_duplicates().Write(pw::bluetooth::emboss::GenericEnableParam::DISABLE);
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_spec::SimpleReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
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().empty()) {
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 = static_cast<uint8_t>(responses);
}
bt::hci::EmbossCommandPacket packet =
bt::hci::EmbossCommandPacket::New<::pw::bluetooth::emboss::InquiryCommandView>(
bt::hci_spec::kInquiry);
auto view = packet.view<::pw::bluetooth::emboss::InquiryCommandWriter>();
view.lap().Write(::pw::bluetooth::emboss::InquiryAccessCode::GIAC);
// Always use the maximum inquiry length, we will time it more accurately.
view.inquiry_length().Write(::bt::hci_spec::kInquiryLengthMax);
view.num_responses().Write(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::EmbossEventPacket& event) {
BT_ASSERT(event.event_code() == ::bt::hci_spec::kInquiryResultEventCode);
auto view = event.view<pw::bluetooth::emboss::InquiryResultEventView>();
for (int i = 0; i < view.num_responses().Read(); i++) {
if (!filter.empty() &&
!filter.compare(0, filter.length(),
bt::DeviceAddressBytes{view.responses()[i].bd_addr()}.ToString())) {
continue;
}
DisplayInquiryResult(view.responses()[i]);
}
return ::bt::hci::CommandChannel::EventCallbackResult::kContinue;
};
event_handler_ids->push_back(cmd_data->cmd_channel()->AddEventHandler(
::bt::hci_spec::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::EmbossEventPacket& event) mutable {
auto view = event.view<::pw::bluetooth::emboss::InquiryCompleteEventView>();
std::cout << fxl::StringPrintf(" Inquiry Complete - status: 0x%02hhx\n",
static_cast<unsigned char>(view.status().Read()));
cleanup_cb();
return ::bt::hci::CommandChannel::EventCallbackResult::kContinue;
};
event_handler_ids->push_back(cmd_data->cmd_channel()->AddEventHandler(
::bt::hci_spec::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_spec::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_spec::CommandStatusEventParams>();
LogCommandResult(return_params.status, id, "Command Status");
if (return_params.status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
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_spec::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().empty()) {
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_spec::kPageScanR1Interval;
uint16_t page_scan_window = ::bt::hci_spec::kPageScanR1Window;
std::string mode_str;
if (cmd_line.GetOptionValue("mode", &mode_str)) {
if (mode_str == "R0") {
page_scan_interval = ::bt::hci_spec::kPageScanR0Interval;
page_scan_window = ::bt::hci_spec::kPageScanR0Window;
} else if (mode_str == "R1") {
page_scan_interval = ::bt::hci_spec::kPageScanR1Interval;
page_scan_window = ::bt::hci_spec::kPageScanR1Window;
} else if (mode_str == "R2") {
page_scan_interval = ::bt::hci_spec::kPageScanR2Interval;
page_scan_window = ::bt::hci_spec::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 < static_cast<uint16_t>(::pw::bluetooth::emboss::ScanInterval::MIN) ||
parsed_interval > static_cast<uint16_t>(::pw::bluetooth::emboss::ScanInterval::MAX)) {
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 < static_cast<uint16_t>(::pw::bluetooth::emboss::ScanWindow::MIN) ||
parsed_window > static_cast<uint16_t>(::pw::bluetooth::emboss::ScanWindow::MAX)) {
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;
}
auto write_activity = ::bt::hci::EmbossCommandPacket::New<
::pw::bluetooth::emboss::WritePageScanActivityCommandWriter>(
::bt::hci_spec::kWritePageScanActivity);
auto activity_params = write_activity.view_t();
activity_params.page_scan_interval().Write(page_scan_interval);
activity_params.page_scan_window().Write(page_scan_window);
auto id = SendCompleteCommand(cmd_data, std::move(write_activity), 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().empty() || !cmd_line.options().empty()) {
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_spec::ReadPageScanActivityReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
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_spec::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().empty()) {
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;
}
pw::bluetooth::emboss::PageScanType page_scan_type =
pw::bluetooth::emboss::PageScanType::STANDARD_SCAN;
std::string type_str;
if (cmd_line.GetOptionValue("type", &type_str)) {
if (type_str == "standard") {
page_scan_type = pw::bluetooth::emboss::PageScanType::STANDARD_SCAN;
} else if (type_str == "interlaced") {
page_scan_type = pw::bluetooth::emboss::PageScanType::INTERLACED_SCAN;
} else {
std::cout << " Unrecognized type: " << type_str << std::endl;
}
}
auto packet =
::bt::hci::EmbossCommandPacket::New<pw::bluetooth::emboss::WritePageScanTypeCommandWriter>(
::bt::hci_spec::kWritePageScanType);
packet.view_t().page_scan_type().Write(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().empty() || !cmd_line.options().empty()) {
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_spec::ReadPageScanTypeReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
complete_cb();
return;
}
if (return_params->page_scan_type == pw::bluetooth::emboss::PageScanType::STANDARD_SCAN) {
std::cout << " Type: standard" << std::endl;
} else if (return_params->page_scan_type ==
pw::bluetooth::emboss::PageScanType::INTERLACED_SCAN) {
std::cout << " Type: interlaced" << std::endl;
} else {
std::cout << " Type: unknown" << std::endl;
}
complete_cb();
};
auto packet = ::bt::hci::CommandPacket::New(::bt::hci_spec::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_spec::ScanEnableType scan_enable = 0x00;
for (const std::string& positional_arg : cmd_line.positional_args()) {
if (positional_arg == "inquiry") {
scan_enable |=
static_cast<::bt::hci_spec::ScanEnableType>(::bt::hci_spec::ScanEnableBit::kInquiry);
} else if (positional_arg == "page") {
scan_enable |=
static_cast<::bt::hci_spec::ScanEnableType>(::bt::hci_spec::ScanEnableBit::kPage);
} else {
std::cout << " Unrecognized positional argument: " << positional_arg << std::endl;
return false;
}
}
auto write_enable =
::bt::hci::EmbossCommandPacket::New<::pw::bluetooth::emboss::WriteScanEnableCommandWriter>(
::bt::hci_spec::kWriteScanEnable);
auto write_enable_view = write_enable.view_t();
write_enable_view.scan_enable().inquiry().Write(
scan_enable & static_cast<uint8_t>(::bt::hci_spec::ScanEnableBit::kInquiry));
write_enable_view.scan_enable().page().Write(
scan_enable & static_cast<uint8_t>(::bt::hci_spec::ScanEnableBit::kPage));
auto id = SendCompleteCommand(cmd_data, std::move(write_enable), 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().empty() || !cmd_line.options().empty()) {
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_spec::ReadScanEnableReturnParams>();
LogCommandResult(return_params->status, id);
if (return_params->status != ::pw::bluetooth::emboss::StatusCode::SUCCESS) {
complete_cb();
return;
}
if (return_params->scan_enable &
static_cast<::bt::hci_spec::ScanEnableType>(::bt::hci_spec::ScanEnableBit::kInquiry)) {
std::cout << " Inquiry scan: enabled" << std::endl;
} else {
std::cout << " Inquiry scan: disabled" << std::endl;
}
if (return_params->scan_enable &
static_cast<::bt::hci_spec::ScanEnableType>(::bt::hci_spec::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_spec::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) {
BT_ASSERT(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