1 // Copyright 2024 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_proxy/rfcomm_channel.h"
16
17 #include <mutex>
18
19 #include "pw_assert/check.h"
20 #include "pw_bluetooth/emboss_util.h"
21 #include "pw_bluetooth/hci_data.emb.h"
22 #include "pw_bluetooth/l2cap_frames.emb.h"
23 #include "pw_bluetooth/rfcomm_frames.emb.h"
24 #include "pw_bluetooth_proxy/internal/l2cap_write_channel.h"
25 #include "pw_bluetooth_proxy/internal/rfcomm_fcs.h"
26 #include "pw_log/log.h"
27 #include "pw_status/try.h"
28
29 namespace pw::bluetooth::proxy {
30
RfcommChannel(RfcommChannel && other)31 RfcommChannel::RfcommChannel(RfcommChannel&& other)
32 : L2capWriteChannel(std::move(static_cast<L2capWriteChannel&>(other))),
33 L2capReadChannel(std::move(static_cast<L2capReadChannel&>(other))),
34 rx_config_(other.rx_config_),
35 tx_config_(other.tx_config_),
36 channel_number_(other.channel_number_) {
37 std::lock_guard lock(mutex_);
38 std::lock_guard other_lock(other.mutex_);
39 rx_credits_ = other.rx_credits_;
40 tx_credits_ = other.tx_credits_;
41 state_ = other.state_;
42 other.state_ = State::kStopped;
43 }
44
Write(pw::span<const uint8_t> payload)45 pw::Status RfcommChannel::Write(pw::span<const uint8_t> payload) {
46 if (state_ == State::kStopped) {
47 return Status::FailedPrecondition();
48 }
49
50 // We always encode credits.
51 const size_t kCreditsFieldSize = 1;
52
53 if (payload.size() > tx_config_.max_information_length - kCreditsFieldSize) {
54 return Status::InvalidArgument();
55 }
56
57 constexpr size_t kMaxShortLength = 0x7f;
58
59 const bool uses_extended_length = payload.size() > kMaxShortLength;
60 const size_t length_extended_size = uses_extended_length ? 1 : 0;
61 const size_t frame_size = emboss::RfcommFrame::MinSizeInBytes() +
62 length_extended_size + kCreditsFieldSize +
63 payload.size();
64
65 // TODO: https://pwbug.dev/365179076 - Support fragmentation.
66 pw::Result<H4PacketWithH4> h4_result = PopulateTxL2capPacket(frame_size);
67 if (!h4_result.ok()) {
68 return h4_result.status();
69 }
70 H4PacketWithH4 h4_packet = std::move(*h4_result);
71
72 PW_TRY_ASSIGN(
73 auto acl,
74 MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan()));
75 auto bframe = emboss::MakeBFrameView(acl.payload().BackingStorage().data(),
76 acl.payload().SizeInBytes());
77 PW_TRY_ASSIGN(auto rfcomm,
78 MakeEmbossWriter<emboss::RfcommFrameWriter>(
79 bframe.payload().BackingStorage().data(),
80 bframe.payload().SizeInBytes()));
81
82 rfcomm.extended_address().Write(true);
83 // TODO: https://pwbug.dev/378691959 - Sniff correct C/R/D from Multiplexer
84 // control commands on RFCOMM channel 0
85 rfcomm.command_response_direction().Write(
86 emboss::RfcommCommandResponseAndDirection::COMMAND_FROM_RESPONDER);
87 rfcomm.channel().Write(channel_number_);
88
89 // Poll/Final = 1 indicates Credits present.
90 rfcomm.control().Write(
91 emboss::RfcommFrameType::
92 UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL);
93 PW_CHECK(rfcomm.has_credits().ValueOrDefault());
94
95 if (!uses_extended_length) {
96 rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::NORMAL);
97 rfcomm.length().Write(payload.size());
98 } else {
99 rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::EXTENDED);
100 rfcomm.length_extended().Write(payload.size());
101 }
102
103 {
104 std::lock_guard lock(mutex_);
105 // TODO: https://pwbug.dev/379184978 - Refill remote side with credits they
106 // have sent. We assume our receiver can handle data without need for
107 // blocking. Revisit when adding downstream flow control to this API.
108 const uint8_t to_refill = rx_config_.credits - rx_credits_;
109 rfcomm.credits().Write(to_refill);
110 rx_credits_ = rx_config_.credits;
111 }
112
113 if (rfcomm.information().SizeInBytes() < payload.size()) {
114 return Status::ResourceExhausted();
115 }
116 PW_CHECK(rfcomm.information().SizeInBytes() == payload.size());
117 std::memcpy(rfcomm.information().BackingStorage().data(),
118 payload.data(),
119 payload.size());
120
121 // UIH frame type:
122 // FCS should be calculated over address and control fields.
123 rfcomm.fcs().Write(RfcommFcs(rfcomm));
124
125 // TODO: https://pwbug.dev/379184978 - Support legacy non-credit based flow
126 // control.
127
128 return QueuePacket(std::move(h4_packet));
129 }
130
DequeuePacket()131 std::optional<H4PacketWithH4> RfcommChannel::DequeuePacket() {
132 std::lock_guard lock(mutex_);
133 if (tx_credits_ == 0) {
134 return std::nullopt;
135 }
136
137 std::optional<H4PacketWithH4> maybe_packet =
138 L2capWriteChannel::DequeuePacket();
139 if (maybe_packet.has_value()) {
140 --tx_credits_;
141 }
142 return maybe_packet;
143 }
144
Create(L2capChannelManager & l2cap_channel_manager,uint16_t connection_handle,Config rx_config,Config tx_config,uint8_t channel_number,pw::Function<void (pw::span<uint8_t> payload)> && receive_fn)145 Result<RfcommChannel> RfcommChannel::Create(
146 L2capChannelManager& l2cap_channel_manager,
147 uint16_t connection_handle,
148 Config rx_config,
149 Config tx_config,
150 uint8_t channel_number,
151 pw::Function<void(pw::span<uint8_t> payload)>&& receive_fn) {
152 if (!L2capWriteChannel::AreValidParameters(connection_handle,
153 tx_config.cid) ||
154 !L2capReadChannel::AreValidParameters(connection_handle, rx_config.cid)) {
155 return Status::InvalidArgument();
156 }
157
158 return RfcommChannel(l2cap_channel_manager,
159 connection_handle,
160 rx_config,
161 tx_config,
162 channel_number,
163 std::move(receive_fn));
164 }
165
HandlePduFromController(pw::span<uint8_t> l2cap_pdu)166 bool RfcommChannel::HandlePduFromController(pw::span<uint8_t> l2cap_pdu) {
167 if (state_ == State::kStopped) {
168 PW_LOG_WARN("Received data on stopped channel, passing on to host.");
169 return false;
170 }
171
172 Result<emboss::BFrameView> bframe_view =
173 MakeEmbossView<emboss::BFrameView>(l2cap_pdu);
174 if (!bframe_view.ok()) {
175 PW_LOG_ERROR(
176 "(CID 0x%X) Buffer is too small for L2CAP B-frame, passing on to host.",
177 local_cid());
178 return false;
179 }
180
181 Result<emboss::RfcommFrameView> rfcomm_view =
182 MakeEmbossView<emboss::RfcommFrameView>(
183 bframe_view->payload().BackingStorage().data(),
184 bframe_view->payload().SizeInBytes());
185 if (!rfcomm_view.ok()) {
186 PW_LOG_ERROR("Unable to parse RFCOMM frame, passing on to host.");
187 return false;
188 }
189
190 if (rfcomm_view->channel().Read() == 0 || !rfcomm_view->uih().Read()) {
191 // Ignore control frames.
192 return false;
193 }
194
195 const uint8_t fcs = RfcommFcs(*rfcomm_view);
196 if (rfcomm_view->fcs().Read() != fcs) {
197 PW_LOG_ERROR("Bad checksum %02X (exp %02X), passing on to host.",
198 rfcomm_view->fcs().Read(),
199 fcs);
200 return false;
201 }
202
203 // TODO: https://pwbug.dev/378691959 - Validate channel, control, C/R,
204 // direction is what is expected.
205
206 if (rfcomm_view->channel().Read() != channel_number_) {
207 PW_LOG_WARN("RFCOMM data not for our channel %d (%d)",
208 rfcomm_view->channel().Read(),
209 channel_number_);
210 }
211
212 bool credits_previously_zero = false;
213 {
214 std::lock_guard lock(mutex_);
215 credits_previously_zero = tx_credits_ == 0;
216 if (rfcomm_view->has_credits().ValueOrDefault()) {
217 tx_credits_ += rfcomm_view->credits().Read();
218 }
219 }
220
221 pw::span<uint8_t> information = pw::span(
222 const_cast<uint8_t*>(rfcomm_view->information().BackingStorage().data()),
223 rfcomm_view->information().SizeInBytes());
224
225 SendPayloadFromControllerToClient(information);
226
227 bool rx_needs_refill = false;
228 {
229 std::lock_guard lock(mutex_);
230 if (rx_credits_ == 0) {
231 PW_LOG_ERROR("Received frame with no rx credits available.");
232 // TODO: https://pwbug.dev/379184978 - Consider dropping channel since
233 // this is invalid state.
234 } else {
235 --rx_credits_;
236 }
237 rx_needs_refill = rx_credits_ < kMinRxCredits;
238 }
239
240 if (rx_needs_refill) {
241 // Send credit update with empty payload to refresh remote credit count.
242 if (const auto status = Write({}); !status.ok()) {
243 PW_LOG_ERROR("Failed to send RFCOMM credits");
244 }
245 }
246
247 if (credits_previously_zero) {
248 ReportPacketsMayBeReadyToSend();
249 }
250
251 return true;
252 }
253
HandlePduFromHost(pw::span<uint8_t>)254 bool RfcommChannel::HandlePduFromHost(pw::span<uint8_t>) { return false; }
255
RfcommChannel(L2capChannelManager & l2cap_channel_manager,uint16_t connection_handle,Config rx_config,Config tx_config,uint8_t channel_number,pw::Function<void (pw::span<uint8_t> payload)> && receive_fn)256 RfcommChannel::RfcommChannel(
257 L2capChannelManager& l2cap_channel_manager,
258 uint16_t connection_handle,
259 Config rx_config,
260 Config tx_config,
261 uint8_t channel_number,
262 pw::Function<void(pw::span<uint8_t> payload)>&& receive_fn)
263 : L2capWriteChannel(l2cap_channel_manager,
264 connection_handle,
265 AclTransportType::kBrEdr,
266 tx_config.cid),
267 L2capReadChannel(l2cap_channel_manager,
268 std::move(receive_fn),
269 connection_handle,
270 rx_config.cid),
271 rx_config_(rx_config),
272 tx_config_(tx_config),
273 channel_number_(channel_number),
274 rx_credits_(rx_config.credits),
275 tx_credits_(tx_config.credits),
276 state_(State::kStarted) {}
277
OnFragmentedPduReceived()278 void RfcommChannel::OnFragmentedPduReceived() {
279 PW_LOG_ERROR(
280 "(CID 0x%X) Fragmented L2CAP frame received (which is not yet "
281 "supported).",
282 local_cid());
283 }
284
285 } // namespace pw::bluetooth::proxy
286