text: // 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/advertising_report_parser.h"
  #include "src/connectivity/bluetooth/core/bt-host/hci/control_packets.h"
  #include "src/connectivity/bluetooth/core/bt-host/hci/util.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
  