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