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 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/iso_stream_server.h"
16
17 #include <lib/fidl/cpp/wire/channel.h>
18 #include <pw_assert/check.h>
19 #include <pw_bluetooth/hci_data.emb.h>
20
21 #include <cinttypes>
22
23 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
24
25 namespace bthost {
26
IsoStreamServer(fidl::InterfaceRequest<fuchsia::bluetooth::le::IsochronousStream> request,fit::callback<void ()> on_closed_cb)27 IsoStreamServer::IsoStreamServer(
28 fidl::InterfaceRequest<fuchsia::bluetooth::le::IsochronousStream> request,
29 fit::callback<void()> on_closed_cb)
30 : ServerBase(this, std::move(request)),
31 on_closed_cb_(std::move(on_closed_cb)),
32 weak_self_(this) {
33 set_error_handler([this](zx_status_t) { OnClosed(); });
34 }
35
OnStreamEstablished(bt::iso::IsoStream::WeakPtr stream_ptr,const bt::iso::CisEstablishedParameters & connection_params)36 void IsoStreamServer::OnStreamEstablished(
37 bt::iso::IsoStream::WeakPtr stream_ptr,
38 const bt::iso::CisEstablishedParameters& connection_params) {
39 bt_log(INFO, "fidl", "CIS established");
40 iso_stream_ = stream_ptr;
41 fuchsia::bluetooth::le::IsochronousStreamOnEstablishedRequest request;
42 request.set_result(ZX_OK);
43 fuchsia::bluetooth::le::CisEstablishedParameters params =
44 bthost::fidl_helpers::CisEstablishedParametersToFidl(connection_params);
45 request.set_established_params(std::move(params));
46 binding()->events().OnEstablished(std::move(request));
47 }
48
OnStreamEstablishmentFailed(pw::bluetooth::emboss::StatusCode status)49 void IsoStreamServer::OnStreamEstablishmentFailed(
50 pw::bluetooth::emboss::StatusCode status) {
51 PW_CHECK(status != pw::bluetooth::emboss::StatusCode::SUCCESS);
52 bt_log(WARN,
53 "fidl",
54 "CIS failed to be established: %u",
55 static_cast<unsigned>(status));
56 fuchsia::bluetooth::le::IsochronousStreamOnEstablishedRequest request;
57 request.set_result(ZX_ERR_INTERNAL);
58 binding()->events().OnEstablished(std::move(request));
59 }
60
SetupDataPath(fuchsia::bluetooth::le::IsochronousStreamSetupDataPathRequest parameters,SetupDataPathCallback fidl_cb)61 void IsoStreamServer::SetupDataPath(
62 fuchsia::bluetooth::le::IsochronousStreamSetupDataPathRequest parameters,
63 SetupDataPathCallback fidl_cb) {
64 pw::bluetooth::emboss::DataPathDirection direction =
65 fidl_helpers::DataPathDirectionFromFidl(parameters.data_direction());
66 const char* direction_as_str =
67 fidl_helpers::DataPathDirectionToString(direction);
68 bt_log(INFO,
69 "fidl",
70 "Request received to set up data path (direction: %s)",
71 direction_as_str);
72 if (direction != pw::bluetooth::emboss::DataPathDirection::OUTPUT) {
73 // We only support Controller => Host at the moment
74 bt_log(WARN,
75 "fidl",
76 "Attempt to set up data path with unsupported direction: %s",
77 direction_as_str);
78 fidl_cb(fpromise::error(ZX_ERR_NOT_SUPPORTED));
79 return;
80 }
81
82 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> codec_id =
83 fidl_helpers::CodecIdFromFidl(parameters.codec_attributes().codec_id());
84 std::optional<std::vector<uint8_t>> codec_configuration;
85 if (parameters.codec_attributes().has_codec_configuration()) {
86 codec_configuration = parameters.codec_attributes().codec_configuration();
87 }
88
89 zx::duration delay(parameters.controller_delay());
90 uint32_t delay_in_us = delay.to_usecs();
91 if (!iso_stream_.has_value()) {
92 bt_log(WARN, "fidl", "data path setup failed (CIS not established)");
93 fidl_cb(fpromise::error(ZX_ERR_BAD_STATE));
94 return;
95 }
96 if (!iso_stream_->is_alive()) {
97 bt_log(INFO, "fidl", "Attempt to set data path after CIS closed");
98 fidl_cb(fpromise::error(ZX_ERR_BAD_STATE));
99 return;
100 }
101
102 auto on_setup_complete_cb =
103 [fidl_cb =
104 std::move(fidl_cb)](bt::iso::IsoStream::SetupDataPathError error) {
105 switch (error) {
106 case bt::iso::IsoStream::kSuccess:
107 bt_log(INFO, "fidl", "data path successfully setup");
108 fidl_cb(fpromise::ok());
109 break;
110 case bt::iso::IsoStream::kStreamAlreadyExists:
111 bt_log(
112 WARN, "fidl", "data path setup failed (stream already setup)");
113 fidl_cb(fpromise::error(ZX_ERR_ALREADY_EXISTS));
114 break;
115 case bt::iso::IsoStream::kCisNotEstablished:
116 bt_log(
117 WARN, "fidl", "data path setup failed (CIS not established)");
118 fidl_cb(fpromise::error(ZX_ERR_BAD_STATE));
119 break;
120 case bt::iso::IsoStream::kInvalidArgs:
121 bt_log(WARN, "fidl", "data path setup failed (invalid parameters)");
122 fidl_cb(fpromise::error(ZX_ERR_INVALID_ARGS));
123 break;
124 case bt::iso::IsoStream::kStreamClosed:
125 bt_log(WARN, "fidl", "data path setup failed (stream closed)");
126 fidl_cb(fpromise::error(ZX_ERR_BAD_STATE));
127 break;
128 default:
129 bt_log(ERROR,
130 "fidl",
131 "Unsupported case in SetupDataPathError: %u",
132 static_cast<unsigned>(error));
133 fidl_cb(fpromise::error(ZX_ERR_INTERNAL));
134 break;
135 }
136 };
137 (*iso_stream_)
138 ->SetupDataPath(
139 direction,
140 codec_id,
141 codec_configuration,
142 delay_in_us,
143 std::move(on_setup_complete_cb),
144 fit::bind_member<&IsoStreamServer::OnIncomingDataAvailable>(this));
145 }
146
SendIncomingPacket(pw::span<const std::byte> packet)147 void IsoStreamServer::SendIncomingPacket(pw::span<const std::byte> packet) {
148 auto view = pw::bluetooth::emboss::MakeIsoDataFramePacketView(packet.data(),
149 packet.size());
150 if (!view.Ok()) {
151 bt_log(ERROR, "fidl", "Failed to parse ISO data frame");
152 // Hanging get will remain unfulfilled
153 return;
154 }
155 PW_CHECK(view.header().pb_flag().Read() ==
156 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
157 "Incomplete SDU received from IsoStream");
158 fuchsia::bluetooth::le::IsochronousStream_Read_Response response;
159
160 size_t data_fragment_size = view.sdu_fragment_size().Read();
161 std::vector<std::uint8_t> data_as_vector(data_fragment_size);
162
163 std::memcpy(data_as_vector.data(),
164 view.iso_sdu_fragment().BackingStorage().data(),
165 data_fragment_size);
166 response.set_data(data_as_vector);
167 response.set_sequence_number(view.packet_sequence_number().Read());
168 response.set_status_flag(fidl_helpers::EmbossIsoPacketStatusFlagToFidl(
169 view.packet_status_flag().Read()));
170
171 PW_CHECK(hanging_read_cb_);
172 hanging_read_cb_(
173 fuchsia::bluetooth::le::IsochronousStream_Read_Result::WithResponse(
174 std::move(response)));
175 hanging_read_cb_ = nullptr;
176 }
177
OnIncomingDataAvailable(pw::span<const std::byte> packet)178 bool IsoStreamServer::OnIncomingDataAvailable(
179 pw::span<const std::byte> packet) {
180 if (!hanging_read_cb_) {
181 // This is not a hard error, but it is a bit suspicious and worth noting. We
182 // should not receive a notification of incoming data unless we have a
183 // hanging Read() operation.
184 bt_log(WARN,
185 "fidl",
186 "Notification of incoming data received with no outstanding read "
187 "operation");
188 return false;
189 }
190 SendIncomingPacket(packet);
191 return true;
192 }
193
Read(ReadCallback callback)194 void IsoStreamServer::Read(ReadCallback callback) {
195 // We should not have more than one outstanding Read()
196 if (hanging_read_cb_) {
197 Close(ZX_ERR_BAD_STATE);
198 return;
199 }
200
201 hanging_read_cb_ = std::move(callback);
202
203 if (iso_stream_.has_value() && iso_stream_->is_alive()) {
204 std::unique_ptr<bt::iso::IsoDataPacket> packet =
205 (*iso_stream_)->ReadNextQueuedIncomingPacket();
206 if (packet) {
207 pw::span<const std::byte> packet_as_span(
208 static_cast<std::byte*>(packet->data()), packet->size());
209 SendIncomingPacket(packet_as_span);
210 return;
211 }
212 }
213 }
214
OnClosed()215 void IsoStreamServer::OnClosed() {
216 if (iso_stream_.has_value() && iso_stream_->is_alive()) {
217 (*iso_stream_)->Close();
218 }
219 // This may free our instance.
220 on_closed_cb_();
221 }
222
Close(zx_status_t epitaph)223 void IsoStreamServer::Close(zx_status_t epitaph) {
224 binding()->Close(epitaph);
225 OnClosed();
226 }
227
handle_unknown_method(uint64_t ordinal,bool has_response)228 void IsoStreamServer::handle_unknown_method(uint64_t ordinal,
229 bool has_response) {
230 bt_log(WARN,
231 "fidl",
232 "Received unknown fidl call %#" PRIx64 " (%s responses)",
233 ordinal,
234 has_response ? "with" : "without");
235 }
236
237 } // namespace bthost
238