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/fragmenter.h"
16
17 #include <endian.h>
18
19 #include <limits>
20 #include <optional>
21
22 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
23 #include "pw_bluetooth_sapphire/internal/host/l2cap/fcs.h"
24 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
25 #include "pw_bluetooth_sapphire/internal/host/transport/acl_data_packet.h"
26
27 namespace bt::l2cap {
28 namespace {
29
30 // ByteBuffer::Copy does not allow copying to a smaller destination for safety.
31 // This clamps the copy size to both the source size and the destination size.
CopyBounded(MutableBufferView destination,const ByteBuffer & source)32 size_t CopyBounded(MutableBufferView destination, const ByteBuffer& source) {
33 const size_t size = std::min(destination.size(), source.size());
34 source.Copy(&destination, 0, size);
35 return size;
36 }
37
38 } // namespace
39
OutboundFrame(ChannelId channel_id,const ByteBuffer & data,FrameCheckSequenceOption fcs_option)40 OutboundFrame::OutboundFrame(ChannelId channel_id,
41 const ByteBuffer& data,
42 FrameCheckSequenceOption fcs_option)
43 : channel_id_(channel_id),
44 data_(data.view()),
45 fcs_option_(fcs_option),
46 fcs_(include_fcs() ? std::optional(MakeFcs()) : std::nullopt) {}
47
size() const48 size_t OutboundFrame::size() const {
49 return sizeof(BasicHeader) + data_.size() +
50 (include_fcs() ? sizeof(FrameCheckSequence) : 0);
51 }
52
WriteToFragment(MutableBufferView fragment_payload,size_t offset)53 void OutboundFrame::WriteToFragment(MutableBufferView fragment_payload,
54 size_t offset) {
55 // Build a table of the pages making up the frame's content, in sorted order.
56 const StaticByteBuffer header_buffer = MakeBasicHeader();
57 const std::optional fcs_buffer =
58 include_fcs() ? std::optional(MakeFcs()) : std::nullopt;
59 const BufferView footer_buffer =
60 fcs_buffer ? fcs_buffer->view() : BufferView();
61 const std::array pages = {
62 header_buffer.view(), data_.view(), footer_buffer, BufferView()};
63 const std::array offsets = {size_t{0},
64 header_buffer.size(),
65 header_buffer.size() + data_.size(),
66 size()};
67 static_assert(pages.size() == offsets.size());
68
69 BT_ASSERT(offset <= size());
70 size_t output_offset = 0;
71
72 // Find the last page whose offset is not greater than the current offset.
73 const auto page_iter =
74 std::prev(std::upper_bound(offsets.begin(), offsets.end(), offset));
75 for (size_t page_index = page_iter - offsets.begin();
76 page_index < pages.size();
77 page_index++) {
78 if (fragment_payload.size() - output_offset == 0) {
79 break;
80 }
81 const auto& page_buffer = pages[page_index];
82 const size_t bytes_copied =
83 CopyBounded(fragment_payload.mutable_view(output_offset),
84 page_buffer.view(offset - offsets[page_index]));
85 offset += bytes_copied;
86 output_offset += bytes_copied;
87 }
88 BT_ASSERT(output_offset <= fragment_payload.size());
89 }
90
MakeBasicHeader() const91 OutboundFrame::BasicHeaderBuffer OutboundFrame::MakeBasicHeader() const {
92 // Length is "the length of the entire L2CAP PDU in octets, excluding the
93 // Length and CID field" (v5.0 Vol 3, Part A, Section 3.3.1)
94 const size_t pdu_content_length = size() - sizeof(BasicHeader);
95 BT_ASSERT_MSG(pdu_content_length <=
96 std::numeric_limits<decltype(BasicHeader::length)>::max(),
97 "PDU payload is too large to be encoded");
98 BasicHeader header = {};
99 header.length = htole16(pdu_content_length);
100 header.channel_id = htole16(channel_id_);
101 BasicHeaderBuffer buffer;
102 buffer.WriteObj(header);
103 return buffer;
104 }
105
MakeFcs() const106 OutboundFrame::FrameCheckSequenceBuffer OutboundFrame::MakeFcs() const {
107 BT_ASSERT(include_fcs());
108 const BasicHeaderBuffer header = MakeBasicHeader();
109 const FrameCheckSequence header_fcs = l2cap::ComputeFcs(header.view());
110 const FrameCheckSequence whole_fcs =
111 l2cap::ComputeFcs(data_.view(), header_fcs);
112 FrameCheckSequenceBuffer buffer;
113 buffer.WriteObj(htole16(whole_fcs.fcs));
114 return buffer;
115 }
116
Fragmenter(hci_spec::ConnectionHandle connection_handle,uint16_t max_acl_payload_size)117 Fragmenter::Fragmenter(hci_spec::ConnectionHandle connection_handle,
118 uint16_t max_acl_payload_size)
119 : connection_handle_(connection_handle),
120 max_acl_payload_size_(max_acl_payload_size) {
121 BT_ASSERT(connection_handle_ <= hci_spec::kConnectionHandleMax);
122 BT_ASSERT(max_acl_payload_size_);
123 BT_ASSERT(max_acl_payload_size_ >= sizeof(BasicHeader));
124 }
125
126 // NOTE(armansito): The following method copies the contents of |data| into ACL
127 // data packets. This copying is currently necessary because the complete HCI
128 // frame (ACL header + payload fragment) we send over the channel to the bt-hci
129 // driver need to be stored contiguously before the call to zx_channel_write.
130 // Plus, we perform the HCI flow-control on the host-stack side which requires
131 // ACL packets to be buffered.
132 //
133 // As our future driver architecture will remove the IPC between the HCI driver
134 // and the host stack, our new interface could support scatter-gather for the
135 // header and the payload. Then, the bt-hci driver could read the payload
136 // fragment directly out of |data| and we would only construct the headers,
137 // removing the extra copy.
138 //
139 // * Current theoretical number of data copies:
140 // 1. service -> L2CAP channel
141 // 2. channel -> fragmenter ->(move) HCI layer
142 // 3. HCI layer ->(zx_channel_write)
143 // 4. (zx_channel_read)-> bt-hci driver
144 // 5. bt-hci driver -> transport driver
145 //
146 // * Potential number of data copies
147 // 1. service -> L2CAP channel
148 // 2. channel -> fragmenter ->(move) HCI layer ->(move) bt-hci driver
149 // if buffering is needed:
150 // 3. bt-hci driver -> transport driver
BuildFrame(ChannelId channel_id,const ByteBuffer & data,FrameCheckSequenceOption fcs_option,bool flushable) const151 PDU Fragmenter::BuildFrame(ChannelId channel_id,
152 const ByteBuffer& data,
153 FrameCheckSequenceOption fcs_option,
154 bool flushable) const {
155 BT_DEBUG_ASSERT(data.size() <= kMaxBasicFramePayloadSize);
156 BT_DEBUG_ASSERT(channel_id);
157
158 OutboundFrame frame(channel_id, data, fcs_option);
159 const size_t frame_size = frame.size();
160 const size_t num_fragments = frame_size / max_acl_payload_size_ +
161 (frame_size % max_acl_payload_size_ ? 1 : 0);
162
163 PDU pdu;
164 size_t processed = 0;
165 for (size_t i = 0; i < num_fragments; i++) {
166 BT_DEBUG_ASSERT(frame_size > processed);
167
168 const size_t fragment_size = std::min(
169 frame_size - processed, static_cast<size_t>(max_acl_payload_size_));
170 auto pbf =
171 (i ? hci_spec::ACLPacketBoundaryFlag::kContinuingFragment
172 : (flushable ? hci_spec::ACLPacketBoundaryFlag::kFirstFlushable
173 : hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable));
174
175 // TODO(armansito): allow passing Active Peripheral Broadcast flag when we
176 // support it.
177 auto acl_packet =
178 hci::ACLDataPacket::New(connection_handle_,
179 pbf,
180 hci_spec::ACLBroadcastFlag::kPointToPoint,
181 static_cast<uint16_t>(fragment_size));
182 BT_DEBUG_ASSERT(acl_packet);
183
184 frame.WriteToFragment(acl_packet->mutable_view()->mutable_payload_data(),
185 processed);
186 processed += fragment_size;
187
188 pdu.AppendFragment(std::move(acl_packet));
189 }
190
191 // The PDU should have been completely processed if we got here.
192 BT_DEBUG_ASSERT(processed == frame_size);
193
194 return pdu;
195 }
196
197 } // namespace bt::l2cap
198