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