blob: 1d7aded2112835849f604e1bf5e5348838b904e3 [file] [log] [blame]
// Copyright 2020 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.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_COMMAND_HANDLER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_COMMAND_HANDLER_H_
#include <lib/fit/defer.h>
#include <lib/fit/result.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/command_channel.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace bt::hci {
// CommandHandler is a wrapper around CommandChannel that abstracts serializing & deserializing of
// command and event packets. Command and event types must implement methods and fields documented
// above each method.
// TODO(fxbug.dev/57720): Types should match PDL generated packet definitions.
//
// This class does not track state regarding commands and events, so it may be used as either a
// temporary or saved object.
class CommandHandler {
public:
explicit CommandHandler(fxl::WeakPtr<CommandChannel> channel) : channel_(channel) {}
// Wrapper around CommandChannel::SendCommand that sends a CommandT and completes on
// CommandT::EventT.
//
// If an event status field indicates an error, that error will be returned instead of the event.
//
// The status event of async commands will be ignored unless it is an error.
//
// CommandT must implement:
// std::unique_ptr<CommandPacket> Encode();
// using EventT = ...;
// static OpCode opcode();
//
// EventT must implement:
// static fit::result<EventT, HostError> Decode(const EventPacket& packet);
// static constexpr uint8_t kEventCode = ...;
template <typename CommandT>
CommandChannel::TransactionId SendCommand(
CommandT command,
fit::callback<void(fit::result<typename CommandT::EventT, Status>)> event_cb) {
// EventT should be the command complete event code. Use SendCommandFinishOnStatus to only
// handle the command status event.
static_assert(CommandT::EventT::kEventCode != kCommandStatusEventCode);
ZX_ASSERT(event_cb);
auto encoded = command.Encode();
auto event_packet_cb = [event_cb = std::move(event_cb)](
auto id, const EventPacket& event_packet) mutable {
ZX_ASSERT_MSG(event_cb, "SendCommand event callback already called (opcode: %#.4x)",
CommandT::opcode());
auto status = event_packet.ToStatus();
if (!status.is_success()) {
event_cb(fit::error(status));
return;
}
// Ignore success status event if it is not the expected completion event.
if (event_packet.event_code() == hci::kCommandStatusEventCode &&
CommandT::EventT::kEventCode != hci::kCommandStatusEventCode) {
bt_log(TRACE, "hci", "received success command status event (opcode: %#.4x)",
CommandT::opcode());
return;
}
ZX_ASSERT(event_packet.event_code() == CommandT::EventT::kEventCode);
auto event_result = CommandT::EventT::Decode(event_packet);
if (event_result.is_error()) {
bt_log(WARN, "hci", "Error decoding event packet (event: %#.2x, error: %s)",
event_packet.event_code(), HostErrorToString(event_result.error()).c_str());
event_cb(fit::error(hci::Status(event_result.error())));
return;
}
event_cb(fit::ok(event_result.take_value()));
};
return channel_->SendCommand(std::move(encoded), std::move(event_packet_cb),
CommandT::EventT::kEventCode);
}
// Same as SendCommand, but completes on the command status event.
// The complete event WILL BE IGNORED if no event handler is registered.
//
// This is useful when the command complete event is already handled by an event handler, and you
// only need to handle command errors.
//
// Example:
// handler.AddEventHandler(fit::bind_member(this, &BrEdrConnectionManager::OnConnectionComplete));
//
// handler.SendCommandFinishOnStatus(
// CreateConnectionCommand{...},
// [](auto result) {
// if (result.is_error()) {
// // Handle error
// return;
// }
// });
template <typename CommandT>
CommandChannel::TransactionId SendCommandFinishOnStatus(
CommandT command, fit::callback<void(fit::result<void, Status>)> status_cb) {
ZX_ASSERT(status_cb);
auto encoded = command.Encode();
auto event_packet_cb = [status_cb = std::move(status_cb)](
auto id, const EventPacket& event_packet) mutable {
ZX_ASSERT(event_packet.event_code() == hci::kCommandStatusEventCode);
auto status = event_packet.ToStatus();
if (!status.is_success()) {
status_cb(fit::error(status));
return;
}
status_cb(fit::ok());
};
return channel_->SendCommand(std::move(encoded), std::move(event_packet_cb),
hci::kCommandStatusEventCode);
}
// Wrapper around CommandChannel::AddEventHandler that calls |handler| with an EventT.
//
// EventT must implement:
// static fit::result<EventT, HostError> Decode(const EventPacket& packet);
// static constexpr uint8_t kEventCode = ...;
template <typename EventT>
CommandChannel::EventHandlerId AddEventHandler(
fit::function<CommandChannel::EventCallbackResult(EventT)> handler) {
ZX_ASSERT(handler);
auto event_packet_cb = [handler = std::move(handler)](const EventPacket& event_packet) {
auto event_result = EventT::Decode(event_packet);
if (event_result.is_error()) {
bt_log(WARN, "hci", "Error decoding event packet (event: %#.2x, error: %s)",
event_packet.event_code(), HostErrorToString(event_result.error()).c_str());
return CommandChannel::EventCallbackResult::kContinue;
}
return handler(event_result.take_value());
};
return channel_->AddEventHandler(EventT::kEventCode, std::move(event_packet_cb));
}
private:
fxl::WeakPtr<CommandChannel> channel_;
};
} // namespace bt::hci
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_COMMAND_HANDLER_H_