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