blob: e2cd93c92a95f40a88a009287776f19c2eef351b [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_L2CAP_COMMAND_HANDLER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_L2CAP_COMMAND_HANDLER_H_
#include <lib/fit/function.h>
#include <memory>
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/signaling_channel.h"
namespace bt::l2cap::internal {
// Wrapper for a signaling channel that sends and receives command
// transactions. It does not hold state. Rather, it:
// - constructs outbound request payloads and decodes/dispatches the received
// response payloads
// - constructs request handlers that decode inbound payloads, registers the
// handlers with SignalingChannel, and creates Responder objects that bind
// response parameters and can be used to send appropriate response commands
// CommandHandler can be constructed for each command to be sent or each
// kind of request to register, and even ephemerally as a temporary around a
// SignalingChannel.
//
// For outbound requests, use the CommandHandler::Send*Request methods. They take parameters to be
// encoded into the request payload (with endian conversion and bounds checking) and a
// *ResponseCallback callback. When a matching response or rejection is received, the callback will
// be passed a *Response object containing the decoded command's parameters. Its |status()| shall
// be checked first to determine whether it's a rejection or response command. Return
// ResponseHandlerAction::kExpectAdditionalResponse if more request responses from the peer will
// follow, or else ResponseHandlerAction::kCompleteOutboundTransaction. Returning
// kCompleteOutboundTransaction will destroy the *ResponseCallback object.
//
// If the underlying SignalingChannel times out waiting for a response, the *ResponseCallback will
// not be called. Instead, the |request_fail_callback| that CommandHandler was constructed with will
// be called.
//
// Example:
// DisconnectionResponseCallback rsp_cb =
// [](const DisconnectionResponse& rsp) {
// if (rsp.status() == Status::kReject) {
// // Do something with rsp.reject_reason()
// } else {
// // Do something with rsp.local_cid() and rsp.remote_cid()
// }
// };
// cmd_handler.SendDisonnectionRequest(remote_cid, local_cid, std::move(rsp_cb));
//
// For inbound requests, use the CommandHandler::Serve*Req methods. They
// each take a request-handling delegate that will be called with decoded
// parameters from the received request, as well as a *Responder object. The
// Responder can be used to send a rejection (|RejectNotUnderstood()| or
// |RejectInvalidChannelId()|) or a matching response (|Send*()|). The channel
// IDs to encode into the response will be bound to the Responder. The Responder
// is only valid during the invocation of the request handler. Its sending
// methods can be called multiple times but it does not check that a malformed
// permutation of commands are sent (e.g. multiple rejections, a rejection
// followed by a response, etc.).
//
// Example:
// DisconnectionRequestCallback req_cb =
// [](ChannelId local_cid, ChannelId remote_cid,
// DisconnectionResponder* responder) {
// // Do something with local_cid and remote_cid
// responder->Send(); // Request's IDs already bound to responder, omit
// // OR:
// responder->RejectInvalidChannelId(); // Idem.
// };
// cmd_handler.ServeDisconnectionRequest(std::move(req_cb));
//
// For both inbound requests and responses, if the received payload data is
// insufficient in size or otherwise malformed, it will be replied to with a
// Reject Not Understood and the corresponding callback will not be invoked.
class CommandHandler {
public:
using Status = SignalingChannel::Status;
using ResponseHandlerAction = SignalingChannelInterface::ResponseHandlerAction;
// Base for all responses received, including Command Reject. If |status()|
// evaluates as |Status::kReject|, then this holds a Command Reject; then
// |reject_reason()| should be read and the data in the derived Response
// object should not be accessed.
class Response {
public:
explicit Response(Status status) : status_(status) {}
Status status() const { return status_; }
// These are valid for reading if the response format contains them;
// otherwise, they read as kInvalidChannelId.
ChannelId local_cid() const { return local_cid_; }
ChannelId remote_cid() const { return remote_cid_; }
// This is valid for reading if |status| is kReject. If its value is
// kInvalidCID, then |local_cid| and |remote_cid| are valid for reading.
RejectReason reject_reason() const { return reject_reason_; }
protected:
friend class CommandHandler;
// Fills the reject fields of |rsp|. Returns true if successful.
bool ParseReject(const ByteBuffer& rej_payload_buf);
Status status_;
ChannelId local_cid_ = kInvalidChannelId;
ChannelId remote_cid_ = kInvalidChannelId;
RejectReason reject_reason_;
};
class DisconnectionResponse final : public Response {
public:
using PayloadT = DisconnectionResponsePayload;
static constexpr const char* kName = "Disconnection Response";
using Response::Response; // Inherit ctor
bool Decode(const ByteBuffer& payload_buf);
};
// Base of response-sending objects passed to request delegates that they can
// use to reply with a corresponding response or a rejection. This base
// includes rejection methods because they can always be sent (but are not
// always a reasonable reply to a given request). This also binds channel IDs
// from the request received and uses them for the outbound response payload,
// so that the delegate can not omit or send incorrect channel IDs.
class Responder {
public:
void RejectNotUnderstood();
void RejectInvalidChannelId();
protected:
explicit Responder(SignalingChannel::Responder* sig_responder,
ChannelId local_cid = kInvalidChannelId,
ChannelId remote_cid = kInvalidChannelId);
virtual ~Responder() = default;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Responder);
ChannelId local_cid() const { return local_cid_; }
ChannelId remote_cid() const { return remote_cid_; }
SignalingChannel::Responder* const sig_responder_;
private:
ChannelId local_cid_;
ChannelId remote_cid_;
};
class DisconnectionResponder final : public Responder {
public:
DisconnectionResponder(SignalingChannel::Responder* sig_responder, ChannelId local_cid,
ChannelId remote_cid);
void Send();
};
// Disconnection Responses never have additional responses.
using DisconnectionResponseCallback = fit::function<void(const DisconnectionResponse& rsp)>;
bool SendDisconnectionRequest(ChannelId remote_cid, ChannelId local_cid,
DisconnectionResponseCallback cb);
using DisconnectionRequestCallback = fit::function<void(ChannelId local_cid, ChannelId remote_cid,
DisconnectionResponder* responder)>;
void ServeDisconnectionRequest(DisconnectionRequestCallback cb);
// |sig| must be valid for the lifetime of this object.
// |command_failed_callback| is called if an outbound request timed out with
// RTX or ERTX timers after retransmission (if configured). The call may come
// after the lifetime of this object.
explicit CommandHandler(SignalingChannelInterface* sig,
fit::closure request_fail_callback = nullptr);
virtual ~CommandHandler() = default;
protected:
// Returns a function that decodes a response status and payload into a |ResponseT| object and
// invokes |rsp_cb| with it.
// |ResponseT| needs to have
// - |Decode| function that accepts a buffer of at least |sizeof(ResponseT::PayloadT)| bytes. If
// it returns false, then decoding failed, no additional responses are expected, and the user
// response handler will not be called.
// - |kName| string literal
//
// TODO(fxbug.dev/36062): Name the return type of CallbackT to make parsing code more readable.
template <class ResponseT, typename CallbackT>
SignalingChannel::ResponseHandler BuildResponseHandler(CallbackT rsp_cb) {
return [rsp_cb = std::move(rsp_cb), fail_cb = request_fail_callback_.share()](
Status status, const ByteBuffer& rsp_payload) {
if (status == Status::kTimeOut) {
bt_log(INFO, "l2cap", "cmd: timed out waiting for \"%s\"", ResponseT::kName);
if (fail_cb) {
fail_cb();
}
return ResponseHandlerAction::kCompleteOutboundTransaction;
}
ResponseT rsp(status);
if (status == Status::kReject) {
if (!rsp.ParseReject(rsp_payload)) {
bt_log(DEBUG, "l2cap", "cmd: ignoring malformed Command Reject, size %zu",
rsp_payload.size());
return ResponseHandlerAction::kCompleteOutboundTransaction;
}
return InvokeResponseCallback(&rsp_cb, std::move(rsp));
}
if (rsp_payload.size() < sizeof(typename ResponseT::PayloadT)) {
bt_log(DEBUG, "l2cap", "cmd: ignoring malformed \"%s\", size %zu (expected %zu)",
ResponseT::kName, rsp_payload.size(), sizeof(typename ResponseT::PayloadT));
return ResponseHandlerAction::kCompleteOutboundTransaction;
}
if (!rsp.Decode(rsp_payload)) {
bt_log(DEBUG, "l2cap", "cmd: ignoring malformed \"%s\", could not decode",
ResponseT::kName);
return ResponseHandlerAction::kCompleteOutboundTransaction;
}
return InvokeResponseCallback(&rsp_cb, std::move(rsp));
};
}
// Invokes |rsp_cb| with |rsp|. Returns ResponseHandlerAction::kCompleteOutboundTransaction for
// "no additional responses expected" if |rsp_cb| returns void, otherwise passes along its return
// result. Used because not all *ResponseCallback types return void (some can request additional
// continuations in their return value).
template <typename CallbackT, class ResponseT>
static CommandHandler::ResponseHandlerAction InvokeResponseCallback(CallbackT* const rsp_cb,
ResponseT rsp) {
if constexpr (std::is_void_v<std::invoke_result_t<CallbackT, ResponseT>>) {
(*rsp_cb)(rsp);
return ResponseHandlerAction::kCompleteOutboundTransaction;
} else {
return (*rsp_cb)(rsp);
}
}
SignalingChannelInterface* sig() const { return sig_; }
private:
SignalingChannelInterface* const sig_; // weak
fit::closure request_fail_callback_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CommandHandler);
};
} // namespace bt::l2cap::internal
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_L2CAP_COMMAND_HANDLER_H_