1 // Copyright 2023 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 #pragma once 16 #include <lib/fit/function.h> 17 18 #include <memory> 19 20 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" 21 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" 22 #include "pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h" 23 24 namespace bt::l2cap::internal { 25 26 // Wrapper for a signaling channel that sends and receives command 27 // transactions. It does not hold state. Rather, it: 28 // - constructs outbound request payloads and decodes/dispatches the received 29 // response payloads 30 // - constructs request handlers that decode inbound payloads, registers the 31 // handlers with SignalingChannel, and creates Responder objects that bind 32 // response parameters and can be used to send appropriate response commands 33 // CommandHandler can be constructed for each command to be sent or each 34 // kind of request to register, and even ephemerally as a temporary around a 35 // SignalingChannel. 36 // 37 // For outbound requests, use the CommandHandler::Send*Request methods. They 38 // take parameters to be encoded into the request payload (with endian 39 // conversion and bounds checking) and a *ResponseCallback callback. When a 40 // matching response or rejection is received, the callback will be passed a 41 // *Response object containing the decoded command's parameters. Its |status()| 42 // shall be checked first to determine whether it's a rejection or response 43 // command. Return ResponseHandlerAction::kExpectAdditionalResponse if more 44 // request responses from the peer will follow, or else 45 // ResponseHandlerAction::kCompleteOutboundTransaction. Returning 46 // kCompleteOutboundTransaction will destroy the *ResponseCallback object. 47 // 48 // If the underlying SignalingChannel times out waiting for a response, the 49 // *ResponseCallback will not be called. Instead, the |request_fail_callback| 50 // that CommandHandler was constructed with will be called. 51 // 52 // Example: 53 // DisconnectionResponseCallback rsp_cb = 54 // [](const DisconnectionResponse& rsp) { 55 // if (rsp.status() == Status::kReject) { 56 // // Do something with rsp.reject_reason() 57 // } else { 58 // // Do something with rsp.local_cid() and rsp.remote_cid() 59 // } 60 // }; 61 // cmd_handler.SendDisonnectionRequest(remote_cid, local_cid, 62 // std::move(rsp_cb)); 63 // 64 // For inbound requests, use the CommandHandler::Serve*Req methods. They 65 // each take a request-handling delegate that will be called with decoded 66 // parameters from the received request, as well as a *Responder object. The 67 // Responder can be used to send a rejection (|RejectNotUnderstood()| or 68 // |RejectInvalidChannelId()|) or a matching response (|Send*()|). The channel 69 // IDs to encode into the response will be bound to the Responder. The Responder 70 // is only valid during the invocation of the request handler. Its sending 71 // methods can be called multiple times but it does not check that a malformed 72 // permutation of commands are sent (e.g. multiple rejections, a rejection 73 // followed by a response, etc.). 74 // 75 // Example: 76 // DisconnectionRequestCallback req_cb = 77 // [](ChannelId local_cid, ChannelId remote_cid, 78 // DisconnectionResponder* responder) { 79 // // Do something with local_cid and remote_cid 80 // responder->Send(); // Request's IDs already bound to responder, omit 81 // // OR: 82 // responder->RejectInvalidChannelId(); // Idem. 83 // }; 84 // cmd_handler.ServeDisconnectionRequest(std::move(req_cb)); 85 // 86 // For both inbound requests and responses, if the received payload data is 87 // insufficient in size or otherwise malformed, it will be replied to with a 88 // Reject Not Understood and the corresponding callback will not be invoked. 89 class CommandHandler { 90 public: 91 using Status = SignalingChannel::Status; 92 using ResponseHandlerAction = 93 SignalingChannelInterface::ResponseHandlerAction; 94 95 // Base for all responses received, including Command Reject. If |status()| 96 // evaluates as |Status::kReject|, then this holds a Command Reject; then 97 // |reject_reason()| should be read and the data in the derived Response 98 // object should not be accessed. 99 class Response { 100 public: Response(Status status)101 explicit Response(Status status) : status_(status) {} 102 status()103 Status status() const { return status_; } 104 105 // These are valid for reading if the response format contains them; 106 // otherwise, they read as kInvalidChannelId. local_cid()107 ChannelId local_cid() const { return local_cid_; } remote_cid()108 ChannelId remote_cid() const { return remote_cid_; } 109 110 // This is valid for reading if |status| is kReject. If its value is 111 // kInvalidCID, then |local_cid| and |remote_cid| are valid for reading. reject_reason()112 RejectReason reject_reason() const { return reject_reason_; } 113 114 protected: 115 friend class CommandHandler; 116 117 // Fills the reject fields of |rsp|. Returns true if successful. 118 bool ParseReject(const ByteBuffer& rej_payload_buf); 119 120 Status status_; 121 ChannelId local_cid_ = kInvalidChannelId; 122 ChannelId remote_cid_ = kInvalidChannelId; 123 RejectReason reject_reason_; 124 }; 125 126 class DisconnectionResponse final : public Response { 127 public: 128 using PayloadT = DisconnectionResponsePayload; 129 static constexpr const char* kName = "Disconnection Response"; 130 131 using Response::Response; // Inherit ctor 132 bool Decode(const ByteBuffer& payload_buf); 133 }; 134 135 // Base of response-sending objects passed to request delegates that they can 136 // use to reply with a corresponding response or a rejection. This base 137 // includes rejection methods because they can always be sent (but are not 138 // always a reasonable reply to a given request). This also binds channel IDs 139 // from the request received and uses them for the outbound response payload, 140 // so that the delegate can not omit or send incorrect channel IDs. 141 class Responder { 142 public: 143 void RejectNotUnderstood(); 144 void RejectInvalidChannelId(); 145 146 protected: 147 explicit Responder(SignalingChannel::Responder* sig_responder, 148 ChannelId local_cid = kInvalidChannelId, 149 ChannelId remote_cid = kInvalidChannelId); 150 virtual ~Responder() = default; 151 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Responder); 152 local_cid()153 ChannelId local_cid() const { return local_cid_; } remote_cid()154 ChannelId remote_cid() const { return remote_cid_; } 155 156 SignalingChannel::Responder* const sig_responder_; 157 158 private: 159 ChannelId local_cid_; 160 ChannelId remote_cid_; 161 }; 162 163 class DisconnectionResponder final : public Responder { 164 public: 165 DisconnectionResponder(SignalingChannel::Responder* sig_responder, 166 ChannelId local_cid, 167 ChannelId remote_cid); 168 169 void Send(); 170 }; 171 172 // Disconnection Responses never have additional responses. 173 using DisconnectionResponseCallback = 174 fit::function<void(const DisconnectionResponse& rsp)>; 175 bool SendDisconnectionRequest(ChannelId remote_cid, 176 ChannelId local_cid, 177 DisconnectionResponseCallback cb); 178 179 using DisconnectionRequestCallback = 180 fit::function<void(ChannelId local_cid, 181 ChannelId remote_cid, 182 DisconnectionResponder* responder)>; 183 void ServeDisconnectionRequest(DisconnectionRequestCallback callback); 184 185 // |sig| must be valid for the lifetime of this object. 186 // |command_failed_callback| is called if an outbound request timed out with 187 // RTX or ERTX timers after retransmission (if configured). The call may come 188 // after the lifetime of this object. 189 explicit CommandHandler(SignalingChannelInterface* sig, 190 fit::closure request_fail_callback = nullptr); 191 virtual ~CommandHandler() = default; 192 193 protected: 194 // Returns a function that decodes a response status and payload into a 195 // |ResponseT| object and invokes |rsp_cb| with it. |ResponseT| needs to have 196 // - |Decode| function that accepts a buffer of at least 197 // |sizeof(ResponseT::PayloadT)| bytes. If 198 // it returns false, then decoding failed, no additional responses are 199 // expected, and the user response handler will not be called. 200 // - |kName| string literal 201 // 202 // TODO(fxbug.dev/42111549): Name the return type of CallbackT to make parsing 203 // code more readable. 204 template <class ResponseT, typename CallbackT> BuildResponseHandler(CallbackT response_cb)205 SignalingChannel::ResponseHandler BuildResponseHandler( 206 CallbackT response_cb) { 207 return [rsp_cb = std::move(response_cb), 208 fail_cb = request_fail_callback_.share()]( 209 Status status, const ByteBuffer& rsp_payload) { 210 if (status == Status::kTimeOut) { 211 bt_log(INFO, 212 "l2cap", 213 "cmd: timed out waiting for \"%s\"", 214 ResponseT::kName); 215 if (fail_cb) { 216 fail_cb(); 217 } 218 return ResponseHandlerAction::kCompleteOutboundTransaction; 219 } 220 221 ResponseT rsp(status); 222 if (status == Status::kReject) { 223 if (!rsp.ParseReject(rsp_payload)) { 224 bt_log(DEBUG, 225 "l2cap", 226 "cmd: ignoring malformed Command Reject, size %zu", 227 rsp_payload.size()); 228 return ResponseHandlerAction::kCompleteOutboundTransaction; 229 } 230 return InvokeResponseCallback(&rsp_cb, std::move(rsp)); 231 } 232 233 if (rsp_payload.size() < sizeof(typename ResponseT::PayloadT)) { 234 bt_log(DEBUG, 235 "l2cap", 236 "cmd: ignoring malformed \"%s\", size %zu (expected %zu)", 237 ResponseT::kName, 238 rsp_payload.size(), 239 sizeof(typename ResponseT::PayloadT)); 240 return ResponseHandlerAction::kCompleteOutboundTransaction; 241 } 242 243 if (!rsp.Decode(rsp_payload)) { 244 bt_log(DEBUG, 245 "l2cap", 246 "cmd: ignoring malformed \"%s\", could not decode", 247 ResponseT::kName); 248 return ResponseHandlerAction::kCompleteOutboundTransaction; 249 } 250 251 return InvokeResponseCallback(&rsp_cb, std::move(rsp)); 252 }; 253 } 254 255 // Invokes |rsp_cb| with |rsp|. Returns 256 // ResponseHandlerAction::kCompleteOutboundTransaction for "no additional 257 // responses expected" if |rsp_cb| returns void, otherwise passes along its 258 // return result. Used because not all *ResponseCallback types return void 259 // (some can request additional continuations in their return value). 260 template <typename CallbackT, class ResponseT> InvokeResponseCallback(CallbackT * const rsp_cb,ResponseT rsp)261 static CommandHandler::ResponseHandlerAction InvokeResponseCallback( 262 CallbackT* const rsp_cb, ResponseT rsp) { 263 if constexpr (std::is_void_v<std::invoke_result_t<CallbackT, ResponseT>>) { 264 (*rsp_cb)(rsp); 265 return ResponseHandlerAction::kCompleteOutboundTransaction; 266 } else { 267 return (*rsp_cb)(rsp); 268 } 269 } 270 sig()271 SignalingChannelInterface* sig() const { return sig_; } 272 273 private: 274 SignalingChannelInterface* const sig_; // weak 275 fit::closure request_fail_callback_; 276 277 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CommandHandler); 278 }; 279 280 } // namespace bt::l2cap::internal 281