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