• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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