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