| // 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 SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_L2CAP_BREDR_COMMAND_HANDLER_H_ |
| #define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_L2CAP_BREDR_COMMAND_HANDLER_H_ |
| |
| #include <lib/fit/function.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/signaling_channel.h" |
| |
| namespace bt { |
| 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: |
| 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 BrEdrCommandHandler; |
| |
| // 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 ConnectionResponse : public Response { |
| public: |
| using PayloadT = ConnectionResponsePayload; |
| static constexpr const char* kName = "Connection Response"; |
| |
| using Response::Response; // Inherit ctor |
| void Decode(const ByteBuffer& payload_buf); |
| |
| ConnectionResult result() const { return result_; } |
| ConnectionStatus conn_status() const { return conn_status_; } |
| |
| private: |
| ConnectionResult result_; |
| ConnectionStatus conn_status_; |
| }; |
| |
| class ConfigurationResponse : public Response { |
| public: |
| using PayloadT = ConfigurationResponsePayload; |
| static constexpr const char* kName = "Configuration Response"; |
| |
| using Response::Response; // Inherit ctor |
| void Decode(const ByteBuffer& payload_buf); |
| |
| uint16_t flags() const { return flags_; } |
| ConfigurationResult result() const { return result_; } |
| const ByteBuffer& options() const { return options_; } |
| |
| // TODO(BT-466): Replace raw option access with abstraction over parsed |
| // options. |
| |
| private: |
| friend class BrEdrCommandHandler; |
| |
| uint16_t flags_; |
| ConfigurationResult result_; |
| |
| // View into the raw options received from the peer. It is only valid for |
| // the duration of the ConfigurationResponseCallback invocation. |
| BufferView options_; |
| }; |
| |
| class DisconnectionResponse : public Response { |
| public: |
| using PayloadT = DisconnectionResponsePayload; |
| static constexpr const char* kName = "Disconnection Response"; |
| |
| using Response::Response; // Inherit ctor |
| void Decode(const ByteBuffer& payload_buf); |
| }; |
| |
| class InformationResponse : public Response { |
| public: |
| using PayloadT = InformationResponsePayload; |
| static constexpr const char* kName = "Information Response"; |
| |
| using Response::Response; // Inherit ctor |
| void Decode(const ByteBuffer& payload_buf); |
| |
| 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. |
| 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; |
| 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 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 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 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 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. |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(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 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: |
| // Returns a function that decodes a response status and payload into a |
| // |ResponseT| object and invokes |cb| with it. |
| // |ResponseT| needs to have |
| // - |Decode| function that accepts a buffer of at least |
| // |sizeof(ResponseT::PayloadT)| bytes |
| // - |kName| string literal |
| template <class ResponseT, typename CallbackT> |
| SignalingChannel::ResponseHandler BuildResponseHandler(CallbackT rsp_cb); |
| |
| SignalingChannelInterface* const sig_; // weak |
| }; |
| |
| } // namespace internal |
| } // namespace l2cap |
| } // namespace bt |
| |
| #endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_L2CAP_BREDR_COMMAND_HANDLER_H_ |