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