xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/fragmenter.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/fragmenter.h"
16 
17 #include <pw_bytes/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   PW_CHECK(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   PW_CHECK(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   PW_CHECK(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 = pw::bytes::ConvertOrderTo(
100       cpp20::endian::little, static_cast<uint16_t>(pdu_content_length));
101   header.channel_id =
102       pw::bytes::ConvertOrderTo(cpp20::endian::little, channel_id_);
103   BasicHeaderBuffer buffer;
104   buffer.WriteObj(header);
105   return buffer;
106 }
107 
MakeFcs() const108 OutboundFrame::FrameCheckSequenceBuffer OutboundFrame::MakeFcs() const {
109   PW_CHECK(include_fcs());
110   const BasicHeaderBuffer header = MakeBasicHeader();
111   const FrameCheckSequence header_fcs = l2cap::ComputeFcs(header.view());
112   const FrameCheckSequence whole_fcs =
113       l2cap::ComputeFcs(data_.view(), header_fcs);
114   FrameCheckSequenceBuffer buffer;
115   buffer.WriteObj(
116       pw::bytes::ConvertOrderTo(cpp20::endian::little, whole_fcs.fcs));
117   return buffer;
118 }
119 
Fragmenter(hci_spec::ConnectionHandle connection_handle,uint16_t max_acl_payload_size)120 Fragmenter::Fragmenter(hci_spec::ConnectionHandle connection_handle,
121                        uint16_t max_acl_payload_size)
122     : connection_handle_(connection_handle),
123       max_acl_payload_size_(max_acl_payload_size) {
124   PW_CHECK(connection_handle_ <= hci_spec::kConnectionHandleMax);
125   PW_CHECK(max_acl_payload_size_);
126   PW_CHECK(max_acl_payload_size_ >= sizeof(BasicHeader));
127 }
128 
129 // NOTE(armansito): The following method copies the contents of |data| into ACL
130 // data packets. This copying is currently necessary because the complete HCI
131 // frame (ACL header + payload fragment) we send over the channel to the bt-hci
132 // driver need to be stored contiguously before the call to zx_channel_write.
133 // Plus, we perform the HCI flow-control on the host-stack side which requires
134 // ACL packets to be buffered.
135 //
136 // As our future driver architecture will remove the IPC between the HCI driver
137 // and the host stack, our new interface could support scatter-gather for the
138 // header and the payload. Then, the bt-hci driver could read the payload
139 // fragment directly out of |data| and we would only construct the headers,
140 // removing the extra copy.
141 //
142 // * Current theoretical number of data copies:
143 //     1. service -> L2CAP channel
144 //     2. channel -> fragmenter ->(move) HCI layer
145 //     3. HCI layer ->(zx_channel_write)
146 //     4. (zx_channel_read)-> bt-hci driver
147 //     5. bt-hci driver -> transport driver
148 //
149 // * Potential number of data copies
150 //     1. service -> L2CAP channel
151 //     2. channel -> fragmenter ->(move) HCI layer ->(move) bt-hci driver
152 //     if buffering is needed:
153 //       3. bt-hci driver -> transport driver
BuildFrame(ChannelId channel_id,const ByteBuffer & data,FrameCheckSequenceOption fcs_option,bool flushable) const154 PDU Fragmenter::BuildFrame(ChannelId channel_id,
155                            const ByteBuffer& data,
156                            FrameCheckSequenceOption fcs_option,
157                            bool flushable) const {
158   PW_DCHECK(data.size() <= kMaxBasicFramePayloadSize);
159   PW_DCHECK(channel_id);
160 
161   OutboundFrame frame(channel_id, data, fcs_option);
162   const size_t frame_size = frame.size();
163   const size_t num_fragments = frame_size / max_acl_payload_size_ +
164                                (frame_size % max_acl_payload_size_ ? 1 : 0);
165 
166   PDU pdu;
167   size_t processed = 0;
168   for (size_t i = 0; i < num_fragments; i++) {
169     PW_DCHECK(frame_size > processed);
170 
171     const size_t fragment_size = std::min(
172         frame_size - processed, static_cast<size_t>(max_acl_payload_size_));
173     auto pbf =
174         (i ? hci_spec::ACLPacketBoundaryFlag::kContinuingFragment
175            : (flushable ? hci_spec::ACLPacketBoundaryFlag::kFirstFlushable
176                         : hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable));
177 
178     // TODO(armansito): allow passing Active Peripheral Broadcast flag when we
179     // support it.
180     auto acl_packet =
181         hci::ACLDataPacket::New(connection_handle_,
182                                 pbf,
183                                 hci_spec::ACLBroadcastFlag::kPointToPoint,
184                                 static_cast<uint16_t>(fragment_size));
185     PW_DCHECK(acl_packet);
186 
187     frame.WriteToFragment(acl_packet->mutable_view()->mutable_payload_data(),
188                           processed);
189     processed += fragment_size;
190 
191     pdu.AppendFragment(std::move(acl_packet));
192   }
193 
194   // The PDU should have been completely processed if we got here.
195   PW_DCHECK(processed == frame_size);
196 
197   return pdu;
198 }
199 
200 }  // namespace bt::l2cap
201