// Copyright 2024 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/fuchsia/host/fidl/iso_stream_server.h" #include #include #include #include #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h" namespace bthost { IsoStreamServer::IsoStreamServer( fidl::InterfaceRequest request, fit::callback on_closed_cb) : ServerBase(this, std::move(request)), on_closed_cb_(std::move(on_closed_cb)), weak_self_(this) { set_error_handler([this](zx_status_t) { OnClosed(); }); } void IsoStreamServer::OnStreamEstablished( bt::iso::IsoStream::WeakPtr stream_ptr, const bt::iso::CisEstablishedParameters& connection_params) { bt_log(INFO, "fidl", "CIS established"); iso_stream_ = stream_ptr; fuchsia::bluetooth::le::IsochronousStreamOnEstablishedRequest request; request.set_result(ZX_OK); fuchsia::bluetooth::le::CisEstablishedParameters params = bthost::fidl_helpers::CisEstablishedParametersToFidl(connection_params); request.set_established_params(std::move(params)); binding()->events().OnEstablished(std::move(request)); } void IsoStreamServer::OnStreamEstablishmentFailed( pw::bluetooth::emboss::StatusCode status) { PW_CHECK(status != pw::bluetooth::emboss::StatusCode::SUCCESS); bt_log(WARN, "fidl", "CIS failed to be established: %u", static_cast(status)); fuchsia::bluetooth::le::IsochronousStreamOnEstablishedRequest request; request.set_result(ZX_ERR_INTERNAL); binding()->events().OnEstablished(std::move(request)); } void IsoStreamServer::SetupDataPath( fuchsia::bluetooth::le::IsochronousStreamSetupDataPathRequest parameters, SetupDataPathCallback fidl_cb) { pw::bluetooth::emboss::DataPathDirection direction = fidl_helpers::DataPathDirectionFromFidl(parameters.data_direction()); const char* direction_as_str = fidl_helpers::DataPathDirectionToString(direction); bt_log(INFO, "fidl", "Request received to set up data path (direction: %s)", direction_as_str); if (direction != pw::bluetooth::emboss::DataPathDirection::OUTPUT) { // We only support Controller => Host at the moment bt_log(WARN, "fidl", "Attempt to set up data path with unsupported direction: %s", direction_as_str); fidl_cb(fpromise::error(ZX_ERR_NOT_SUPPORTED)); return; } bt::StaticPacket codec_id = fidl_helpers::CodecIdFromFidl(parameters.codec_attributes().codec_id()); std::optional> codec_configuration; if (parameters.codec_attributes().has_codec_configuration()) { codec_configuration = parameters.codec_attributes().codec_configuration(); } zx::duration delay(parameters.controller_delay()); uint32_t delay_in_us = delay.to_usecs(); if (!iso_stream_.has_value()) { bt_log(WARN, "fidl", "data path setup failed (CIS not established)"); fidl_cb(fpromise::error(ZX_ERR_BAD_STATE)); return; } if (!iso_stream_->is_alive()) { bt_log(INFO, "fidl", "Attempt to set data path after CIS closed"); fidl_cb(fpromise::error(ZX_ERR_BAD_STATE)); return; } auto on_setup_complete_cb = [fidl_cb = std::move(fidl_cb)](bt::iso::IsoStream::SetupDataPathError error) { switch (error) { case bt::iso::IsoStream::kSuccess: bt_log(INFO, "fidl", "data path successfully setup"); fidl_cb(fpromise::ok()); break; case bt::iso::IsoStream::kStreamAlreadyExists: bt_log( WARN, "fidl", "data path setup failed (stream already setup)"); fidl_cb(fpromise::error(ZX_ERR_ALREADY_EXISTS)); break; case bt::iso::IsoStream::kCisNotEstablished: bt_log( WARN, "fidl", "data path setup failed (CIS not established)"); fidl_cb(fpromise::error(ZX_ERR_BAD_STATE)); break; case bt::iso::IsoStream::kInvalidArgs: bt_log(WARN, "fidl", "data path setup failed (invalid parameters)"); fidl_cb(fpromise::error(ZX_ERR_INVALID_ARGS)); break; case bt::iso::IsoStream::kStreamClosed: bt_log(WARN, "fidl", "data path setup failed (stream closed)"); fidl_cb(fpromise::error(ZX_ERR_BAD_STATE)); break; default: bt_log(ERROR, "fidl", "Unsupported case in SetupDataPathError: %u", static_cast(error)); fidl_cb(fpromise::error(ZX_ERR_INTERNAL)); break; } }; (*iso_stream_) ->SetupDataPath( direction, codec_id, codec_configuration, delay_in_us, std::move(on_setup_complete_cb), fit::bind_member<&IsoStreamServer::OnIncomingDataAvailable>(this)); } void IsoStreamServer::SendIncomingPacket(pw::span packet) { auto view = pw::bluetooth::emboss::MakeIsoDataFramePacketView(packet.data(), packet.size()); if (!view.Ok()) { bt_log(ERROR, "fidl", "Failed to parse ISO data frame"); // Hanging get will remain unfulfilled return; } PW_CHECK(view.header().pb_flag().Read() == pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU, "Incomplete SDU received from IsoStream"); fuchsia::bluetooth::le::IsochronousStream_Read_Response response; size_t data_fragment_size = view.sdu_fragment_size().Read(); std::vector data_as_vector(data_fragment_size); std::memcpy(data_as_vector.data(), view.iso_sdu_fragment().BackingStorage().data(), data_fragment_size); response.set_data(data_as_vector); response.set_sequence_number(view.packet_sequence_number().Read()); response.set_status_flag(fidl_helpers::EmbossIsoPacketStatusFlagToFidl( view.packet_status_flag().Read())); PW_CHECK(hanging_read_cb_); hanging_read_cb_( fuchsia::bluetooth::le::IsochronousStream_Read_Result::WithResponse( std::move(response))); hanging_read_cb_ = nullptr; } bool IsoStreamServer::OnIncomingDataAvailable( pw::span packet) { if (!hanging_read_cb_) { // This is not a hard error, but it is a bit suspicious and worth noting. We // should not receive a notification of incoming data unless we have a // hanging Read() operation. bt_log(WARN, "fidl", "Notification of incoming data received with no outstanding read " "operation"); return false; } SendIncomingPacket(packet); return true; } void IsoStreamServer::Read(ReadCallback callback) { // We should not have more than one outstanding Read() if (hanging_read_cb_) { Close(ZX_ERR_BAD_STATE); return; } hanging_read_cb_ = std::move(callback); if (iso_stream_.has_value() && iso_stream_->is_alive()) { std::unique_ptr packet = (*iso_stream_)->ReadNextQueuedIncomingPacket(); if (packet) { pw::span packet_as_span( static_cast(packet->data()), packet->size()); SendIncomingPacket(packet_as_span); return; } } } void IsoStreamServer::OnClosed() { if (iso_stream_.has_value() && iso_stream_->is_alive()) { (*iso_stream_)->Close(); } // This may free our instance. on_closed_cb_(); } void IsoStreamServer::Close(zx_status_t epitaph) { binding()->Close(epitaph); OnClosed(); } void IsoStreamServer::handle_unknown_method(uint64_t ordinal, bool has_response) { bt_log(WARN, "fidl", "Received unknown fidl call %#" PRIx64 " (%s responses)", ordinal, has_response ? "with" : "without"); } } // namespace bthost