1 // Copyright 2024 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
16 #include "pw_bluetooth_sapphire/internal/host/iso/iso_inbound_packet_assembler.h"
17
18 #include <pw_assert/check.h>
19 #include <pw_bluetooth/hci_data.emb.h>
20
21 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
22
23 namespace bt::iso {
24
ProcessNext(pw::span<const std::byte> packet)25 void IsoInboundPacketAssembler::ProcessNext(pw::span<const std::byte> packet) {
26 auto packet_view = pw::bluetooth::emboss::MakeIsoDataFramePacketView(
27 packet.data(), packet.size());
28
29 // This should have been checked by the caller.
30 PW_CHECK(packet_view.Ok());
31
32 pw::bluetooth::emboss::IsoDataPbFlag pb_flag =
33 packet_view.header().pb_flag().Read();
34 if ((pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU) ||
35 (pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::FIRST_FRAGMENT)) {
36 // If this is the start of an SDU, we shouldn't have anything in the buffer
37 if (!assembly_buffer_.empty()) {
38 bt_log(ERROR, "iso", "Incomplete ISO packet received - discarding");
39 assembly_buffer_.clear();
40 }
41 }
42
43 if (pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU) {
44 PW_CHECK(complete_packet_handler_);
45 complete_packet_handler_(packet);
46 return;
47 }
48
49 // When we encounter the first fragment of an SDU, we just copy everything
50 // into the temporary buffer.
51 if (pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::FIRST_FRAGMENT) {
52 // Make sure our buffer has sufficient space to hold the entire assembled
53 // ISO SDU frame
54 size_t assembled_frame_size = packet_view.sdu_fragment_offset().Read() +
55 packet_view.iso_sdu_length().Read();
56 if (assembled_frame_size > assembly_buffer_.capacity()) {
57 assembly_buffer_.reserve(assembled_frame_size);
58 }
59
60 assembly_buffer_.resize(packet.size());
61 std::copy(packet.begin(), packet.end(), assembly_buffer_.begin());
62 return;
63 }
64
65 PW_CHECK((pb_flag ==
66 pw::bluetooth::emboss::IsoDataPbFlag::INTERMEDIATE_FRAGMENT) ||
67 (pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::LAST_FRAGMENT));
68 if (!AppendFragment(packet)) {
69 return;
70 }
71
72 if (pb_flag == pw::bluetooth::emboss::IsoDataPbFlag::LAST_FRAGMENT) {
73 pw::span<const std::byte> assembly_buffer_span(assembly_buffer_.data(),
74 assembly_buffer_.size());
75 complete_packet_handler_(assembly_buffer_span);
76 }
77 }
78
AppendFragment(pw::span<const std::byte> packet)79 bool IsoInboundPacketAssembler::AppendFragment(
80 pw::span<const std::byte> packet) {
81 // Make sure we have previously received fragments
82 if (assembly_buffer_.empty()) {
83 bt_log(
84 ERROR, "iso", "Out-of-order ISO packet fragment received - discarding");
85 return false;
86 }
87
88 auto assembly_buffer_view = pw::bluetooth::emboss::MakeIsoDataFramePacketView(
89 assembly_buffer_.data(), assembly_buffer_.size());
90 PW_DCHECK(assembly_buffer_view.Ok());
91
92 auto fragment_view = pw::bluetooth::emboss::MakeIsoDataFramePacketView(
93 packet.data(), packet.size());
94 PW_DCHECK(fragment_view.Ok());
95
96 // A failure here would indicate that packets are being incorrectly routed to
97 // the appropriate stream (since we should be using the connection handle to
98 // figure out where to send the packet).
99 PW_CHECK(assembly_buffer_view.header().connection_handle().Read() ==
100 fragment_view.header().connection_handle().Read());
101
102 size_t total_sdu_bytes_received =
103 assembly_buffer_view.sdu_fragment_size().Read() +
104 fragment_view.sdu_fragment_size().Read();
105 size_t complete_sdu_length = assembly_buffer_view.iso_sdu_length().Read();
106
107 // Verify that the total amount of SDU data received does not exceed that
108 // specified in the header from the FIRST_FRAGMENT.
109 if (total_sdu_bytes_received > complete_sdu_length) {
110 bt_log(ERROR,
111 "iso",
112 "Invalid data fragments received, exceed total SDU length - "
113 "discarding");
114 assembly_buffer_.clear();
115 return false;
116 }
117
118 bool is_last_fragment = fragment_view.header().pb_flag().Read() ==
119 pw::bluetooth::emboss::IsoDataPbFlag::LAST_FRAGMENT;
120 if (is_last_fragment && (total_sdu_bytes_received < complete_sdu_length)) {
121 bt_log(ERROR,
122 "iso",
123 "Insufficient data fragments received (%zu bytes received, expected "
124 "%zu) - discarding",
125 total_sdu_bytes_received,
126 complete_sdu_length);
127 assembly_buffer_.clear();
128 return false;
129 }
130
131 // Append the SDU data
132 assembly_buffer_.insert(
133 assembly_buffer_.end(),
134 fragment_view.iso_sdu_fragment().BackingStorage().begin(),
135 fragment_view.iso_sdu_fragment().BackingStorage().end());
136
137 // Update the header fields so they are as consistent as possible
138 assembly_buffer_view.header().data_total_length().Write(
139 assembly_buffer_.size() - assembly_buffer_view.hdr_size().Read());
140 if (is_last_fragment) {
141 assembly_buffer_view.header().pb_flag().Write(
142 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU);
143 }
144 return true;
145 }
146
147 } // namespace bt::iso
148