blob: 2e15cae307baeef4a6ca1f368dbf49e2ca00b829 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sco_connection.h"
namespace bt::sco {
ScoConnection::ScoConnection(std::unique_ptr<hci::Connection> connection,
fit::closure deactivated_cb,
hci_spec::SynchronousConnectionParameters parameters,
hci::ScoDataChannel* channel)
: active_(false),
connection_(std::move(connection)),
deactivated_cb_(std::move(deactivated_cb)),
channel_(channel),
parameters_(parameters),
weak_ptr_factory_(this) {
ZX_ASSERT(connection_);
ZX_ASSERT(!channel_ ||
channel_->max_data_length() <= hci_spec::kMaxSynchronousDataPacketPayloadSize);
handle_ = connection_->handle();
connection_->set_peer_disconnect_callback([this](auto, auto) {
// Notifies activator that this connection has been disconnected.
// Activator will call Deactivate().
Close();
});
}
fbl::RefPtr<ScoConnection> ScoConnection::Create(
std::unique_ptr<hci::Connection> connection, fit::closure deactivated_cb,
hci_spec::SynchronousConnectionParameters parameters, hci::ScoDataChannel* channel) {
return fbl::AdoptRef(
new ScoConnection(std::move(connection), std::move(deactivated_cb), parameters, channel));
}
ScoConnection::UniqueId ScoConnection::unique_id() const {
// HCI connection handles are unique per controller.
return handle();
}
ScoConnection::UniqueId ScoConnection::id() const { return unique_id(); }
void ScoConnection::Close() {
bt_log(TRACE, "gap-sco", "closing sco connection (handle: %.4x)", handle());
bool active = active_;
CleanUp();
if (!active) {
return;
}
ZX_ASSERT(activator_closed_cb_);
// Move cb out of this, since cb may destroy this.
auto cb = std::move(activator_closed_cb_);
cb();
}
bool ScoConnection::Activate(fit::closure rx_callback, fit::closure closed_callback) {
// TODO(fxbug.dev/58458): Handle Activate() called on a connection that has been closed already.
ZX_ASSERT(closed_callback);
ZX_ASSERT(!active_);
ZX_ASSERT(rx_callback);
activator_closed_cb_ = std::move(closed_callback);
rx_callback_ = std::move(rx_callback);
active_ = true;
if (channel_ && parameters_.input_data_path == hci_spec::ScoDataPath::kHci) {
channel_->RegisterConnection(weak_ptr_factory_.GetWeakPtr());
}
return true;
}
void ScoConnection::Deactivate() {
bt_log(TRACE, "gap-sco", "deactivating sco connection (handle: %.4x)", handle());
CleanUp();
if (deactivated_cb_) {
// Move cb out of this, since cb may destroy this.
auto cb = std::move(deactivated_cb_);
cb();
}
}
uint16_t ScoConnection::max_tx_sdu_size() const {
return channel_ ? channel_->max_data_length() : 0u;
}
bool ScoConnection::Send(ByteBufferPtr payload) {
if (!active_) {
bt_log(WARN, "gap-sco", "dropping SCO packet for inactive connection (handle: %#.4x)", handle_);
return false;
}
if (!channel_) {
bt_log(WARN, "gap-sco", "dropping SCO packet because HCI SCO is not supported (handle: %#.4x)",
handle_);
return false;
}
if (payload->size() > channel_->max_data_length()) {
bt_log(WARN, "gap-sco",
"dropping SCO packet larger than the buffer data packet length (packet size: %zu, max "
"data length: "
"%hu)",
payload->size(), channel_->max_data_length());
return false;
}
outbound_queue_.push(std::move(payload));
// Notify ScoDataChannel that a packet is available. This is only necessary for the first
// packet of an empty queue (flow control will poll this connection otherwise).
if (outbound_queue_.size() == 1u) {
channel_->OnOutboundPacketReadable();
}
return true;
}
std::unique_ptr<hci::ScoDataPacket> ScoConnection::Read() {
if (inbound_queue_.empty()) {
return nullptr;
}
std::unique_ptr<hci::ScoDataPacket> packet = std::move(inbound_queue_.front());
inbound_queue_.pop();
return packet;
}
hci_spec::SynchronousConnectionParameters ScoConnection::parameters() { return parameters_; }
std::unique_ptr<hci::ScoDataPacket> ScoConnection::GetNextOutboundPacket() {
if (outbound_queue_.empty()) {
return nullptr;
}
std::unique_ptr<hci::ScoDataPacket> out =
hci::ScoDataPacket::New(handle(), static_cast<uint8_t>(outbound_queue_.front()->size()));
if (!out) {
bt_log(ERROR, "gap-sco", "failed to allocate SCO data packet");
return nullptr;
}
out->mutable_view()->mutable_payload_data().Write(outbound_queue_.front()->view());
outbound_queue_.pop();
return out;
}
void ScoConnection::ReceiveInboundPacket(std::unique_ptr<hci::ScoDataPacket> packet) {
ZX_ASSERT(packet->connection_handle() == handle_);
if (!active_ || !rx_callback_) {
bt_log(TRACE, "gap-sco", "dropping inbound SCO packet");
return;
}
inbound_queue_.push(std::move(packet));
// It's only necessary to notify activator of the first packet queued (flow control will poll this
// connection otherwise).
if (inbound_queue_.size() == 1u) {
rx_callback_();
}
}
void ScoConnection::OnHciError() {
// Notify activator that this connection should be deactivated.
Close();
}
void ScoConnection::CleanUp() {
if (active_ && channel_ && parameters_.input_data_path == hci_spec::ScoDataPath::kHci) {
channel_->UnregisterConnection(handle_);
}
active_ = false;
connection_ = nullptr;
}
} // namespace bt::sco