xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/signaling_channel.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 #include "pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h"
16 
17 #include <lib/fit/function.h>
18 #include <pw_bytes/endian.h>
19 
20 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
23 #include "pw_bluetooth_sapphire/internal/host/l2cap/channel.h"
24 
25 namespace bt::l2cap::internal {
26 
SignalingChannel(Channel::WeakPtr chan,pw::bluetooth::emboss::ConnectionRole role,pw::async::Dispatcher & dispatcher)27 SignalingChannel::SignalingChannel(Channel::WeakPtr chan,
28                                    pw::bluetooth::emboss::ConnectionRole role,
29                                    pw::async::Dispatcher& dispatcher)
30     : pw_dispatcher_(dispatcher),
31       is_open_(true),
32       chan_(std::move(chan)),
33       role_(role),
34       next_cmd_id_(0x01),
35       weak_self_(this) {
36   PW_DCHECK(chan_);
37   PW_DCHECK(chan_->id() == kSignalingChannelId ||
38             chan_->id() == kLESignalingChannelId);
39 
40   // Note: No need to guard against out-of-thread access as these callbacks are
41   // called on the L2CAP thread.
42   auto self = weak_self_.GetWeakPtr();
43   chan_->Activate(
44       [self](ByteBufferPtr sdu) {
45         if (self.is_alive())
46           self->OnRxBFrame(std::move(sdu));
47       },
48       [self] {
49         if (self.is_alive())
50           self->OnChannelClosed();
51       });
52 }
53 
SendRequest(CommandCode req_code,const ByteBuffer & payload,ResponseHandler cb)54 bool SignalingChannel::SendRequest(CommandCode req_code,
55                                    const ByteBuffer& payload,
56                                    ResponseHandler cb) {
57   PW_CHECK(cb);
58 
59   // Command identifiers for pending requests are assumed to be unique across
60   // all types of requests and reused by order of least recent use. See v5.0
61   // Vol 3, Part A Section 4.
62   //
63   // Uniqueness across different command types: "Within each signaling channel a
64   // different Identifier shall be used for each successive command"
65   // Reuse order: "the Identifier may be recycled if all other Identifiers have
66   // subsequently been used"
67   const CommandId initial_id = GetNextCommandId();
68   CommandId id;
69   for (id = initial_id; IsCommandPending(id);) {
70     id = GetNextCommandId();
71 
72     if (id == initial_id) {
73       bt_log(
74           WARN,
75           "l2cap",
76           "sig: all valid command IDs in use for pending requests; can't send "
77           "request %#.2x",
78           req_code);
79       return false;
80     }
81   }
82 
83   auto command_packet = BuildPacket(req_code, id, payload);
84 
85   CommandCode response_code = req_code + 1;
86   EnqueueResponse(*command_packet, id, response_code, std::move(cb));
87 
88   return Send(std::move(command_packet));
89 }
90 
ServeRequest(CommandCode req_code,RequestDelegate cb)91 void SignalingChannel::ServeRequest(CommandCode req_code, RequestDelegate cb) {
92   PW_CHECK(!IsSupportedResponse(req_code));
93   PW_CHECK(cb);
94   inbound_handlers_[req_code] = std::move(cb);
95 }
96 
EnqueueResponse(const ByteBuffer & request_packet,CommandId id,CommandCode response_command_code,ResponseHandler cb)97 void SignalingChannel::EnqueueResponse(const ByteBuffer& request_packet,
98                                        CommandId id,
99                                        CommandCode response_command_code,
100                                        ResponseHandler cb) {
101   PW_CHECK(IsSupportedResponse(response_command_code));
102 
103   const auto [iter, inserted] = pending_commands_.try_emplace(
104       id, request_packet, response_command_code, std::move(cb), pw_dispatcher_);
105   PW_CHECK(inserted);
106 
107   // Start the RTX timer per Core Spec v5.0, Volume 3, Part A, Sec 6.2.1 which
108   // will call OnResponseTimeout when it expires. This timer is canceled if the
109   // response is received before expiry because OnRxResponse destroys its
110   // containing PendingCommand.
111   SmartTask& rtx_task = iter->second.response_timeout_task;
112   rtx_task.set_function(
113       [this, id](pw::async::Context /*ctx*/, pw::Status status) {
114         if (status.ok()) {
115           OnResponseTimeout(id, /*retransmit=*/true);
116         }
117       });
118   iter->second.timer_duration = kSignalingChannelResponseTimeout;
119   rtx_task.PostAfter(iter->second.timer_duration);
120 }
121 
IsCommandPending(CommandId id) const122 bool SignalingChannel::IsCommandPending(CommandId id) const {
123   return pending_commands_.find(id) != pending_commands_.end();
124 }
125 
ResponderImpl(SignalingChannel * sig,CommandCode code,CommandId id)126 SignalingChannel::ResponderImpl::ResponderImpl(SignalingChannel* sig,
127                                                CommandCode code,
128                                                CommandId id)
129     : sig_(sig), code_(code), id_(id) {
130   PW_DCHECK(sig_);
131 }
132 
Send(const ByteBuffer & rsp_payload)133 void SignalingChannel::ResponderImpl::Send(const ByteBuffer& rsp_payload) {
134   sig()->SendPacket(code_, id_, rsp_payload);
135 }
136 
RejectNotUnderstood()137 void SignalingChannel::ResponderImpl::RejectNotUnderstood() {
138   sig()->SendCommandReject(id_, RejectReason::kNotUnderstood, BufferView());
139 }
140 
RejectInvalidChannelId(ChannelId local_cid,ChannelId remote_cid)141 void SignalingChannel::ResponderImpl::RejectInvalidChannelId(
142     ChannelId local_cid, ChannelId remote_cid) {
143   uint16_t ids[2];
144   ids[0] = pw::bytes::ConvertOrderTo(cpp20::endian::little, local_cid);
145   ids[1] = pw::bytes::ConvertOrderTo(cpp20::endian::little, remote_cid);
146   sig()->SendCommandReject(
147       id_, RejectReason::kInvalidCID, BufferView(ids, sizeof(ids)));
148 }
149 
SendPacket(CommandCode code,uint8_t identifier,const ByteBuffer & data)150 bool SignalingChannel::SendPacket(CommandCode code,
151                                   uint8_t identifier,
152                                   const ByteBuffer& data) {
153   return Send(BuildPacket(code, identifier, data));
154 }
155 
HandlePacket(const SignalingPacket & packet)156 bool SignalingChannel::HandlePacket(const SignalingPacket& packet) {
157   if (IsSupportedResponse(packet.header().code)) {
158     OnRxResponse(packet);
159     return true;
160   }
161 
162   // Handle request commands from remote.
163   const auto iter = inbound_handlers_.find(packet.header().code);
164   if (iter != inbound_handlers_.end()) {
165     ResponderImpl responder(this, packet.header().code + 1, packet.header().id);
166     iter->second(packet.payload_data(), &responder);
167     return true;
168   }
169 
170   bt_log(DEBUG,
171          "l2cap",
172          "sig: ignoring unsupported code %#.2x",
173          packet.header().code);
174 
175   return false;
176 }
177 
OnRxResponse(const SignalingPacket & packet)178 void SignalingChannel::OnRxResponse(const SignalingPacket& packet) {
179   auto cmd_id = packet.header().id;
180   auto iter = pending_commands_.find(cmd_id);
181   if (iter == pending_commands_.end()) {
182     // Core Spec v5.2, Vol 3, Part A, Section 4.1: L2CAP_COMMAND_REJECT_RSP
183     // packets should NOT be sent in response to an identified response packet.
184     bt_log(TRACE,
185            "l2cap",
186            "sig: ignoring unexpected response, id %#.2x",
187            packet.header().id);
188     return;
189   }
190 
191   Status status;
192   auto command_node = pending_commands_.extract(iter);
193   auto& pending_command = command_node.mapped();
194   if (packet.header().code == pending_command.response_code) {
195     status = Status::kSuccess;
196   } else if (packet.header().code == kCommandRejectCode) {
197     status = Status::kReject;
198   } else {
199     bt_log(WARN,
200            "l2cap",
201            "sig: response (id %#.2x) has unexpected code %#.2x",
202            packet.header().id,
203            packet.header().code);
204     SendCommandReject(cmd_id, RejectReason::kNotUnderstood, BufferView());
205     return;
206   }
207 
208   if (pending_command.response_handler(status, packet.payload_data()) ==
209       ResponseHandlerAction::kCompleteOutboundTransaction) {
210     // Note that the response handler may have destroyed |this| at this point.
211     return;
212   }
213 
214   // Renew the timer as an ERTX timer per Core Spec v5.0, Volume 3, Part A,
215   // Sec 6.2.2.
216   // TODO(fxbug.dev/42132982): Limit the number of times the ERTX timer is reset
217   // so that total timeout duration is <= 300 seconds.
218   pending_command.response_timeout_task.Cancel();
219   pending_command.timer_duration = kPwSignalingChannelExtendedResponseTimeout;
220   // Don't retransmit after an ERTX timeout as the peer has already indicated
221   // that it received the request and has been given a large amount of time.
222   pending_command.response_timeout_task.set_function(
223       [this, cmd_id](pw::async::Context /*ctx*/, pw::Status task_status) {
224         if (task_status.ok()) {
225           OnResponseTimeout(cmd_id, /*retransmit=*/false);
226         }
227       });
228   pending_command.response_timeout_task.PostAfter(
229       pending_command.timer_duration);
230   pending_commands_.insert(std::move(command_node));
231 }
232 
OnResponseTimeout(CommandId id,bool retransmit)233 void SignalingChannel::OnResponseTimeout(CommandId id, bool retransmit) {
234   auto iter = pending_commands_.find(id);
235   PW_CHECK(iter != pending_commands_.end());
236 
237   if (!retransmit ||
238       iter->second.transmit_count == kMaxSignalingChannelTransmissions) {
239     auto node = pending_commands_.extract(iter);
240     ResponseHandler& response_handler = node.mapped().response_handler;
241     response_handler(Status::kTimeOut, BufferView());
242     return;
243   }
244 
245   RetransmitPendingCommand(iter->second);
246 }
247 
Send(ByteBufferPtr packet)248 bool SignalingChannel::Send(ByteBufferPtr packet) {
249   PW_DCHECK(packet);
250   PW_DCHECK(packet->size() >= sizeof(CommandHeader));
251 
252   if (!is_open())
253     return false;
254 
255   // While 0x00 is an illegal command identifier (see v5.0, Vol 3, Part A,
256   // Section 4) we don't assert that here. When we receive a command that uses
257   // 0 as the identifier, we reject the command and use that identifier in the
258   // response rather than assert and crash.
259   [[maybe_unused]] SignalingPacket reply(
260       packet.get(), packet->size() - sizeof(CommandHeader));
261   PW_DCHECK(reply.header().code);
262   PW_DCHECK(reply.payload_size() ==
263             pw::bytes::ConvertOrderFrom(cpp20::endian::little,
264                                         reply.header().length));
265   PW_DCHECK(chan_);
266 
267   return chan_->Send(std::move(packet));
268 }
269 
BuildPacket(CommandCode code,uint8_t identifier,const ByteBuffer & data)270 ByteBufferPtr SignalingChannel::BuildPacket(CommandCode code,
271                                             uint8_t identifier,
272                                             const ByteBuffer& data) {
273   PW_DCHECK(data.size() <= std::numeric_limits<uint16_t>::max());
274 
275   auto buffer = NewBuffer(sizeof(CommandHeader) + data.size());
276   PW_CHECK(buffer);
277 
278   MutableSignalingPacket packet(buffer.get(), data.size());
279   packet.mutable_header()->code = code;
280   packet.mutable_header()->id = identifier;
281   packet.mutable_header()->length = pw::bytes::ConvertOrderTo(
282       cpp20::endian::little, static_cast<uint16_t>(data.size()));
283   packet.mutable_payload_data().Write(data);
284 
285   return buffer;
286 }
287 
SendCommandReject(uint8_t identifier,RejectReason reason,const ByteBuffer & data)288 bool SignalingChannel::SendCommandReject(uint8_t identifier,
289                                          RejectReason reason,
290                                          const ByteBuffer& data) {
291   PW_DCHECK(data.size() <= kCommandRejectMaxDataLength);
292 
293   constexpr size_t kMaxPayloadLength =
294       sizeof(CommandRejectPayload) + kCommandRejectMaxDataLength;
295   StaticByteBuffer<kMaxPayloadLength> rej_buf;
296 
297   MutablePacketView<CommandRejectPayload> reject(&rej_buf, data.size());
298   reject.mutable_header()->reason = pw::bytes::ConvertOrderTo(
299       cpp20::endian::little, static_cast<uint16_t>(reason));
300   reject.mutable_payload_data().Write(data);
301 
302   return SendPacket(kCommandRejectCode, identifier, reject.data());
303 }
304 
GetNextCommandId()305 CommandId SignalingChannel::GetNextCommandId() {
306   // Recycling identifiers is permitted and only 0x00 is invalid (v5.0 Vol 3,
307   // Part A, Section 4).
308   const auto cmd = next_cmd_id_++;
309   if (next_cmd_id_ == kInvalidCommandId) {
310     next_cmd_id_ = 0x01;
311   }
312 
313   return cmd;
314 }
315 
OnChannelClosed()316 void SignalingChannel::OnChannelClosed() {
317   PW_DCHECK(is_open());
318 
319   is_open_ = false;
320 }
321 
OnRxBFrame(ByteBufferPtr sdu)322 void SignalingChannel::OnRxBFrame(ByteBufferPtr sdu) {
323   if (!is_open())
324     return;
325 
326   DecodeRxUnit(
327       std::move(sdu),
328       fit::bind_member<&SignalingChannel::CheckAndDispatchPacket>(this));
329 }
330 
CheckAndDispatchPacket(const SignalingPacket & packet)331 void SignalingChannel::CheckAndDispatchPacket(const SignalingPacket& packet) {
332   if (packet.size() > mtu()) {
333     // Respond with our signaling MTU.
334     uint16_t rsp_mtu = pw::bytes::ConvertOrderTo(cpp20::endian::little, mtu());
335     BufferView rej_data(&rsp_mtu, sizeof(rsp_mtu));
336     SendCommandReject(
337         packet.header().id, RejectReason::kSignalingMTUExceeded, rej_data);
338   } else if (!packet.header().id) {
339     // "Signaling identifier 0x00 is an illegal identifier and shall never be
340     // used in any command" (v5.0, Vol 3, Part A, Section 4).
341     bt_log(DEBUG, "l2cap", "illegal signaling cmd ID: 0x00; reject");
342     SendCommandReject(
343         packet.header().id, RejectReason::kNotUnderstood, BufferView());
344   } else if (!HandlePacket(packet)) {
345     SendCommandReject(
346         packet.header().id, RejectReason::kNotUnderstood, BufferView());
347   }
348 }
349 
RetransmitPendingCommand(PendingCommand & pending_command)350 void SignalingChannel::RetransmitPendingCommand(
351     PendingCommand& pending_command) {
352   pending_command.response_timeout_task.Cancel();
353 
354   pending_command.transmit_count++;
355   bt_log(TRACE,
356          "l2cap",
357          "retransmitting pending command (transmission #: %zu)",
358          pending_command.transmit_count);
359 
360   // "If a duplicate Request message is sent, the RTX timeout value shall be
361   // reset to a new value at least double the previous value". (Core Spec v5.1,
362   // Vol 3, Part A, Sec 6.2.1).
363   pending_command.timer_duration *= 2;
364 
365   pending_command.response_timeout_task.PostAfter(
366       pending_command.timer_duration);
367 
368   Send(std::make_unique<DynamicByteBuffer>(*pending_command.command_packet));
369 }
370 
371 }  // namespace bt::l2cap::internal
372