xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/recombiner.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/recombiner.h"
16 
17 #include <pw_bytes/endian.h>
18 
19 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
21 
22 namespace bt::l2cap {
23 namespace {
24 
GetBasicHeader(const hci::ACLDataPacket & fragment)25 const BasicHeader& GetBasicHeader(const hci::ACLDataPacket& fragment) {
26   PW_DCHECK(fragment.packet_boundary_flag() !=
27             hci_spec::ACLPacketBoundaryFlag::kContinuingFragment);
28   return fragment.view().payload<BasicHeader>();
29 }
30 
31 }  // namespace
32 
Recombiner(hci_spec::ConnectionHandle handle)33 Recombiner::Recombiner(hci_spec::ConnectionHandle handle) : handle_(handle) {}
34 
ConsumeFragment(hci::ACLDataPacketPtr fragment)35 Recombiner::Result Recombiner::ConsumeFragment(hci::ACLDataPacketPtr fragment) {
36   PW_DCHECK(fragment);
37   PW_DCHECK(fragment->connection_handle() == handle_);
38   TRACE_DURATION("bluetooth", "Recombiner::AddFragment");
39 
40   if (!recombination_) {
41     return ProcessFirstFragment(std::move(fragment));
42   }
43 
44   // If we received a new initial packet without completing the recombination,
45   // then drop the entire last sequence.
46   if (fragment->packet_boundary_flag() !=
47       hci_spec::ACLPacketBoundaryFlag::kContinuingFragment) {
48     bt_log(
49         WARN, "l2cap", "expected continuing fragment! (handle: %.4x)", handle_);
50     ClearRecombination();
51 
52     // Try to initiate a new starting sequence with |fragment|.
53     auto result = ProcessFirstFragment(std::move(fragment));
54 
55     // Report an error for the dropped frame, even if there was no error
56     // processing |fragment| itself.
57     result.frames_dropped = true;
58     return result;
59   }
60 
61   recombination_->accumulated_length += fragment->view().payload_size();
62   recombination_->pdu.AppendFragment(std::move(fragment));
63   BeginTrace();
64 
65   if (recombination_->accumulated_length >
66       recombination_->expected_frame_length) {
67     bt_log(
68         WARN, "l2cap", "continuing fragment too long! (handle: %.4x)", handle_);
69     ClearRecombination();
70 
71     // Drop |fragment| since a continuing fragment cannot begin a sequence.
72     return {.pdu = {}, .frames_dropped = true};
73   }
74 
75   if (recombination_->accumulated_length ==
76       recombination_->expected_frame_length) {
77     // The frame is complete!
78     auto pdu = std::move(recombination_->pdu);
79     ClearRecombination();
80     return {.pdu = {std::move(pdu)}, .frames_dropped = false};
81   }
82 
83   // The frame is not complete yet.
84   return {.pdu = {}, .frames_dropped = false};
85 }
86 
ProcessFirstFragment(hci::ACLDataPacketPtr fragment)87 Recombiner::Result Recombiner::ProcessFirstFragment(
88     hci::ACLDataPacketPtr fragment) {
89   PW_DCHECK(fragment);
90   PW_DCHECK(!recombination_);
91 
92   // The first fragment needs to at least contain the Basic L2CAP header and
93   // should not be a continuation fragment.
94   size_t current_length = fragment->view().payload_size();
95   if (fragment->packet_boundary_flag() ==
96           hci_spec::ACLPacketBoundaryFlag::kContinuingFragment ||
97       current_length < sizeof(BasicHeader)) {
98     bt_log(DEBUG, "l2cap", "bad first fragment (size: %zu)", current_length);
99     return {.pdu = {}, .frames_dropped = true};
100   }
101 
102   // TODO(armansito): Also validate that the controller honors the HCI packet
103   // boundary flag contract for the controller-to-host flow direction.
104 
105   size_t expected_frame_length =
106       static_cast<uint16_t>(pw::bytes::ConvertOrderFrom(
107           cpp20::endian::little, GetBasicHeader(*fragment).length)) +
108       sizeof(BasicHeader);
109 
110   if (current_length > expected_frame_length) {
111     bt_log(DEBUG,
112            "l2cap",
113            "fragment malformed: payload too long (expected length: %zu, "
114            "fragment length: %zu)",
115            expected_frame_length,
116            current_length);
117     return {.pdu = {}, .frames_dropped = true};
118   }
119 
120   // We can start building a PDU.
121   PDU pdu;
122   pdu.AppendFragment(std::move(fragment));
123 
124   if (current_length == expected_frame_length) {
125     // The PDU is complete.
126     return {.pdu = {std::move(pdu)}, .frames_dropped = false};
127   }
128 
129   // We need to recombine multiple fragments to obtain a complete PDU.
130   BeginTrace();
131   recombination_ = {
132       .pdu = std::move(pdu),
133       .expected_frame_length = expected_frame_length,
134       .accumulated_length = current_length,
135   };
136   return {.pdu = {}, .frames_dropped = false};
137 }
138 
ClearRecombination()139 void Recombiner::ClearRecombination() {
140   PW_DCHECK(recombination_);
141   if (recombination_->pdu.is_valid()) {
142     bt_log(DEBUG,
143            "l2cap",
144            "recombiner dropped packet (fragments: %zu, expected length: %zu, "
145            "accumulated length: "
146            "%zu, handle: %.4x)",
147            recombination_->pdu.fragment_count(),
148            recombination_->expected_frame_length,
149            recombination_->accumulated_length,
150            handle_);
151   }
152   recombination_.reset();
153   EndTraces();
154 }
155 
BeginTrace()156 void Recombiner::BeginTrace() {
157   if (!TRACE_ENABLED()) {
158     return;
159   }
160   trace_flow_id_t flow_id = TRACE_NONCE();
161   TRACE_FLOW_BEGIN(
162       "bluetooth", "Recombiner buffered ACL data fragment", flow_id);
163   trace_ids_.push_back(flow_id);
164 }
165 
EndTraces()166 void Recombiner::EndTraces() {
167   if (!TRACE_ENABLED()) {
168     return;
169   }
170   for ([[maybe_unused]] auto flow_id : trace_ids_) {
171     TRACE_FLOW_END(
172         "bluetooth", "Recombiner buffered ACL data fragment", flow_id);
173   }
174   trace_ids_.clear();
175 }
176 
177 }  // namespace bt::l2cap
178