blob: 6f40ba4615811df1094d021d0fd290b64c73b9c3 [file] [log] [blame]
// Copyright 2018 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 GARNET_DRIVERS_BLUETOOTH_LIB_L2CAP_BREDR_COMMAND_HANDLER_H_
#define GARNET_DRIVERS_BLUETOOTH_LIB_L2CAP_BREDR_COMMAND_HANDLER_H_
#include <lib/fit/function.h>
#include "garnet/drivers/bluetooth/lib/common/byte_buffer.h"
#include "garnet/drivers/bluetooth/lib/l2cap/l2cap.h"
#include "garnet/drivers/bluetooth/lib/l2cap/signaling_channel.h"
namespace btlib {
namespace l2cap {
namespace internal {
// Wrapper for a BR/EDR 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
// BrEdrCommandHandler 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 BrEdrCommandHandler::Send*Req 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 true in the *ResponseCallback if additional responses are
// expected from the peer for the request sent.
//
// 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()
// }
// return false; // No further responses expected for this transaction
// };
// cmd_handler.SendDisconnectionRequest(id0, id1, std::move(rsp_cb));
//
// For inbound requests, use the BrEdrCommandHandler::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 BrEdrCommandHandler final {
public:
using Status = SignalingChannel::Status;
// 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:
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 BrEdrCommandHandler;
Status status_;
ChannelId local_cid_ = kInvalidChannelId;
ChannelId remote_cid_ = kInvalidChannelId;
RejectReason reject_reason_;
};
class ConnectionResponse : public Response {
public:
ConnectionResult result() const { return result_; }
ConnectionStatus conn_status() const { return conn_status_; }
private:
friend class BrEdrCommandHandler;
ConnectionResult result_;
ConnectionStatus conn_status_;
};
class ConfigurationResponse : public Response {
public:
uint16_t flags() const { return flags_; }
ConfigurationResult result() const { return result_; }
const common::ByteBuffer& options() const { return options_; }
// TODO(NET-1084): Replace raw payload access with abstraction over parsed
// options.
private:
friend class BrEdrCommandHandler;
uint16_t flags_;
ConfigurationResult result_;
// View into the payload received from the peer. It is only valid for the
// duration of the ConfigurationResponseCallback invocation.
common::BufferView options_;
};
using DisconnectionResponse = Response;
class InformationResponse : public Response {
public:
InformationType type() const { return type_; }
InformationResult result() const { return result_; }
uint16_t connectionless_mtu() const {
ZX_DEBUG_ASSERT(type() == InformationType::kConnectionlessMTU);
return data_.As<uint16_t>();
}
uint32_t extended_features() const {
ZX_DEBUG_ASSERT(type() == InformationType::kExtendedFeaturesSupported);
return data_.As<uint32_t>();
}
uint64_t fixed_channels() const {
ZX_DEBUG_ASSERT(type() == InformationType::kFixedChannelsSupported);
return data_.As<uint64_t>();
}
private:
friend class BrEdrCommandHandler;
InformationType type_;
InformationResult result_;
// View into the payload received from the peer in host endianness. It is
// only valid for the duration of the InformationResponseCallback
// invocation.
common::BufferView data_;
};
using ConnectionResponseCallback =
fit::function<bool(const ConnectionResponse& rsp)>;
using ConfigurationResponseCallback =
fit::function<bool(const ConfigurationResponse& rsp)>;
using DisconnectionResponseCallback =
fit::function<bool(const DisconnectionResponse& rsp)>;
using InformationResponseCallback =
fit::function<bool(const InformationResponse& rsp)>;
// 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;
FXL_DISALLOW_COPY_AND_ASSIGN(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 ConnectionResponder : public Responder {
public:
ConnectionResponder(SignalingChannel::Responder* sig_responder,
ChannelId remote_cid);
void Send(ChannelId local_cid, ConnectionResult result,
ConnectionStatus status);
};
class ConfigurationResponder : public Responder {
public:
ConfigurationResponder(SignalingChannel::Responder* sig_responder,
ChannelId local_cid);
void Send(ChannelId remote_cid, uint16_t flags, ConfigurationResult result,
const common::ByteBuffer& data);
// TODO(NET-1084): Add builder abstraction for configuration options
};
class DisconnectionResponder : public Responder {
public:
DisconnectionResponder(SignalingChannel::Responder* sig_responder,
ChannelId local_cid, ChannelId remote_cid);
void Send();
};
class InformationResponder : public Responder {
public:
InformationResponder(SignalingChannel::Responder* sig_responder,
InformationType type);
void SendNotSupported();
void SendConnectionlessMtu(uint16_t mtu);
void SendExtendedFeaturesSupported(ExtendedFeatures extended_features);
void SendFixedChannelsSupported(FixedChannelsSupported channels_supported);
private:
void Send(InformationResult result, const common::ByteBuffer& data);
InformationType type_;
};
using ConnectionRequestCallback = fit::function<void(
PSM psm, ChannelId remote_cid, ConnectionResponder* responder)>;
using ConfigurationRequestCallback = fit::function<void(
ChannelId local_cid, uint16_t flags, const common::ByteBuffer& options,
ConfigurationResponder* responder)>;
using DisconnectionRequestCallback =
fit::function<void(ChannelId local_cid, ChannelId remote_cid,
DisconnectionResponder* responder)>;
using InformationRequestCallback = fit::function<void(
InformationType type, InformationResponder* responder)>;
// |sig| must be valid for the lifetime of this object
explicit BrEdrCommandHandler(SignalingChannelInterface* sig);
~BrEdrCommandHandler() = default;
// Disallow copy even though there's no state because having multiple
// BrEdrCommandHandlers in the same scope is likely due to a bug or is at
// least redundant.
FXL_DISALLOW_COPY_AND_ASSIGN(BrEdrCommandHandler);
// Outbound request sending methods. Response callbacks are required to be
// non-empty. The callbacks are wrapped and moved into the SignalingChannel
// and may outlive BrEdrCommandHandler.
bool SendConnectionRequest(uint16_t psm, ChannelId local_cid,
ConnectionResponseCallback cb);
bool SendConfigurationRequest(ChannelId remote_cid, uint16_t flags,
const common::ByteBuffer& options,
ConfigurationResponseCallback cb);
bool SendDisconnectionRequest(ChannelId remote_cid, ChannelId local_cid,
DisconnectionResponseCallback cb);
bool SendInformationRequest(InformationType type,
InformationResponseCallback cb);
// Inbound request delegate registration methods. The callbacks are wrapped
// and moved into the SignalingChannel and may outlive BrEdrCommandHandler. It
// is expected that any request delegates registered will span the lifetime of
// its signaling channel and hence link, so no unregistration is provided.
// However each call to register will replace any currently registered request
// delegate.
void ServeConnectionRequest(ConnectionRequestCallback cb);
void ServeConfigurationRequest(ConfigurationRequestCallback cb);
void ServeDisconnectionRequest(DisconnectionRequestCallback cb);
void ServeInformationRequest(InformationRequestCallback cb);
private:
// Fills the reject fields of |rsp|. Returns true if successful.
bool ParseReject(const common::ByteBuffer& rej_payload_buf,
Response* rsp) const;
SignalingChannelInterface* const sig_; // weak
};
} // namespace internal
} // namespace l2cap
} // namespace btlib
#endif // GARNET_DRIVERS_BLUETOOTH_LIB_L2CAP_BREDR_COMMAND_HANDLER_H_