blob: e40fb71cd80299b15b1f9291becc5af0263230a6 [file] [log] [blame] [edit]
// 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_proxy/rfcomm_channel.h"
#include <mutex>
#include "pw_assert/check.h"
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth/rfcomm_frames.emb.h"
#include "pw_bluetooth_proxy/internal/logical_transport.h"
#include "pw_bluetooth_proxy/internal/rfcomm_fcs.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_bluetooth_proxy/single_channel_proxy.h"
#include "pw_log/log.h"
namespace pw::bluetooth::proxy {
RfcommChannel::RfcommChannel(RfcommChannel&& other)
: SingleChannelProxy(std::move(static_cast<SingleChannelProxy&>(other))),
rx_config_(other.rx_config_),
tx_config_(other.tx_config_),
channel_number_(other.channel_number_),
payload_from_controller_fn_(
std::move(other.payload_from_controller_fn_)) {
{
std::lock_guard lock(rx_mutex_);
std::lock_guard other_lock(other.rx_mutex_);
rx_credits_ = other.rx_credits_;
}
{
std::lock_guard lock(tx_mutex_);
std::lock_guard other_lock(other.tx_mutex_);
tx_credits_ = other.tx_credits_;
}
}
namespace {
// We always encode credits.
constexpr size_t kCreditsFieldSize = 1;
} // namespace
Status RfcommChannel::DoCheckWriteParameter(pw::multibuf::MultiBuf& payload) {
if (payload.size() > tx_config_.max_information_length - kCreditsFieldSize) {
PW_LOG_WARN("Payload (%zu bytes) is too large. So will not process.",
payload.size());
return Status::InvalidArgument();
}
return pw::OkStatus();
}
std::optional<H4PacketWithH4> RfcommChannel::GenerateNextTxPacket() {
if (state() != State::kRunning || PayloadQueueEmpty()) {
return std::nullopt;
}
ConstByteSpan payload = GetFrontPayloadSpan();
constexpr size_t kMaxShortLength = 0x7f;
const bool uses_extended_length = payload.size() > kMaxShortLength;
const size_t length_extended_size = uses_extended_length ? 1 : 0;
const size_t frame_size = emboss::RfcommFrame::MinSizeInBytes() +
length_extended_size + kCreditsFieldSize +
payload.size();
// TODO: https://pwbug.dev/379337260 - Support fragmentation.
pw::Result<H4PacketWithH4> h4_result = PopulateTxL2capPacket(frame_size);
if (!h4_result.ok()) {
return std::nullopt;
}
H4PacketWithH4 h4_packet = std::move(*h4_result);
Result<emboss::AclDataFrameWriter> result2 =
MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
PW_CHECK(result2.ok());
emboss::AclDataFrameWriter acl = result2.value();
// At this point we assume we can return a PDU with the payload.
PopFrontPayload();
emboss::BFrameWriter bframe = emboss::MakeBFrameView(
acl.payload().BackingStorage().data(), acl.payload().SizeInBytes());
PW_CHECK(bframe.IsComplete());
emboss::RfcommFrameWriter rfcomm = emboss::MakeRfcommFrameView(
bframe.payload().BackingStorage().data(), bframe.payload().SizeInBytes());
PW_CHECK(bframe.payload().SizeInBytes() >= frame_size);
rfcomm.extended_address().Write(true);
// TODO: https://pwbug.dev/378691959 - Sniff correct C/R/D from Multiplexer
// control commands on RFCOMM channel 0
rfcomm.command_response_direction().Write(
emboss::RfcommCommandResponseAndDirection::COMMAND_FROM_RESPONDER);
rfcomm.channel().Write(channel_number_);
// Poll/Final = 1 indicates Credits present.
rfcomm.control().Write(
emboss::RfcommFrameType::
UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL);
PW_CHECK(rfcomm.has_credits().ValueOrDefault());
if (!uses_extended_length) {
rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::NORMAL);
rfcomm.length().Write(payload.size());
} else {
rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::EXTENDED);
rfcomm.length_extended().Write(payload.size());
}
{
std::lock_guard lock(rx_mutex_);
// TODO: https://pwbug.dev/379184978 - Refill remote side with credits they
// have sent. We assume our receiver can handle data without need for
// blocking. Revisit when adding downstream flow control to this API.
const uint8_t to_refill = rx_config_.credits - rx_credits_;
rfcomm.credits().Write(to_refill);
rx_credits_ = rx_config_.credits;
}
if (rfcomm.information().SizeInBytes() < payload.size()) {
return std::nullopt;
}
PW_CHECK(rfcomm.information().SizeInBytes() == payload.size());
PW_CHECK(TryToCopyToEmbossStruct(
/*emboss_dest=*/rfcomm.information(),
/*src=*/payload));
// UIH frame type:
// FCS should be calculated over address and control fields.
rfcomm.fcs().Write(RfcommFcs(rfcomm));
PW_CHECK(acl.Ok());
PW_CHECK(bframe.Ok());
PW_CHECK(rfcomm.Ok());
return h4_packet;
}
std::optional<H4PacketWithH4> RfcommChannel::DequeuePacket() {
std::lock_guard lock(tx_mutex_);
if (tx_credits_ == 0) {
return std::nullopt;
}
std::optional<H4PacketWithH4> maybe_packet = L2capChannel::DequeuePacket();
if (maybe_packet.has_value()) {
--tx_credits_;
}
return maybe_packet;
}
Result<RfcommChannel> RfcommChannel::Create(
L2capChannelManager& l2cap_channel_manager,
multibuf::MultiBufAllocator& rx_multibuf_allocator,
uint16_t connection_handle,
Config rx_config,
Config tx_config,
uint8_t channel_number,
Function<void(multibuf::MultiBuf&& payload)>&& payload_from_controller_fn,
ChannelEventCallback&& event_fn) {
if (!AreValidParameters(/*connection_handle=*/connection_handle,
/*local_cid=*/rx_config.cid,
/*remote_cid=*/tx_config.cid)) {
return Status::InvalidArgument();
}
return RfcommChannel(l2cap_channel_manager,
rx_multibuf_allocator,
connection_handle,
rx_config,
tx_config,
channel_number,
std::move(payload_from_controller_fn),
std::move(event_fn));
}
bool RfcommChannel::DoHandlePduFromController(pw::span<uint8_t> l2cap_pdu) {
if (state() != State::kRunning) {
PW_LOG_WARN("Received data on stopped channel, passing on to host.");
return false;
}
Result<emboss::BFrameView> bframe_view =
MakeEmbossView<emboss::BFrameView>(l2cap_pdu);
if (!bframe_view.ok()) {
PW_LOG_ERROR(
"(CID %#x) Buffer is too small for L2CAP B-frame, passing on to host.",
local_cid());
return false;
}
Result<emboss::RfcommFrameView> rfcomm_view =
MakeEmbossView<emboss::RfcommFrameView>(
bframe_view->payload().BackingStorage().data(),
bframe_view->payload().SizeInBytes());
if (!rfcomm_view.ok()) {
PW_LOG_ERROR("Unable to parse RFCOMM frame, passing on to host.");
return false;
}
if (rfcomm_view->channel().Read() == 0 || !rfcomm_view->uih().Read()) {
// Ignore control frames.
return false;
}
const uint8_t fcs = RfcommFcs(*rfcomm_view);
if (rfcomm_view->fcs().Read() != fcs) {
PW_LOG_ERROR("Bad checksum %02X (exp %02X), passing on to host.",
rfcomm_view->fcs().Read(),
fcs);
return false;
}
// TODO: https://pwbug.dev/378691959 - Validate channel, control, C/R,
// direction is what is expected.
if (rfcomm_view->channel().Read() != channel_number_) {
PW_LOG_WARN("RFCOMM data not for our channel %d (%d)",
rfcomm_view->channel().Read(),
channel_number_);
}
bool credits_previously_zero = false;
{
std::lock_guard lock(tx_mutex_);
credits_previously_zero = tx_credits_ == 0;
if (rfcomm_view->has_credits().ValueOrDefault()) {
tx_credits_ += rfcomm_view->credits().Read();
}
}
pw::span<uint8_t> information = pw::span(
const_cast<uint8_t*>(rfcomm_view->information().BackingStorage().data()),
rfcomm_view->information().SizeInBytes());
SendPayloadFromControllerToClient(information);
bool rx_needs_refill = false;
{
std::lock_guard lock(rx_mutex_);
if (rx_credits_ == 0) {
PW_LOG_ERROR("Received frame with no rx credits available.");
// TODO: https://pwbug.dev/379184978 - Consider dropping channel since
// this is invalid state.
} else {
--rx_credits_;
}
rx_needs_refill = rx_credits_ < kMinRxCredits;
}
if (rx_needs_refill) {
// Send credit update with empty payload to refresh remote credit count.
if (const auto status = Write(pw::multibuf::MultiBuf{}).status;
!status.ok()) {
PW_LOG_ERROR("Failed to send RFCOMM credits");
}
}
if (credits_previously_zero) {
ReportNewTxPacketsOrCredits();
}
return true;
}
bool RfcommChannel::SendPayloadFromControllerToClient(
pw::span<uint8_t> payload) {
PW_CHECK(rx_multibuf_allocator());
std::optional<multibuf::MultiBuf> buffer =
rx_multibuf_allocator()->AllocateContiguous(payload.size());
if (!buffer) {
PW_LOG_ERROR(
"(CID %#x) Rx MultiBuf allocator out of memory. So stopping "
"channel "
"and reporting it needs to be closed.",
local_cid());
StopAndSendEvent(L2capChannelEvent::kRxOutOfMemory);
return true;
}
StatusWithSize status = buffer->CopyFrom(/*source=*/as_bytes(payload),
/*position=*/0);
PW_CHECK_OK(status);
if (payload_from_controller_fn_) {
payload_from_controller_fn_(std::move(*buffer));
}
return true;
}
bool RfcommChannel::HandlePduFromHost(pw::span<uint8_t>) { return false; }
RfcommChannel::RfcommChannel(
L2capChannelManager& l2cap_channel_manager,
multibuf::MultiBufAllocator& rx_multibuf_allocator,
uint16_t connection_handle,
Config rx_config,
Config tx_config,
uint8_t channel_number,
Function<void(multibuf::MultiBuf&& payload)>&& payload_from_controller_fn,
ChannelEventCallback&& event_fn)
: SingleChannelProxy(l2cap_channel_manager,
&rx_multibuf_allocator,
/*connection_handle=*/connection_handle,
/*transport=*/AclTransportType::kBrEdr,
/*local_cid=*/rx_config.cid,
/*remote_cid=*/tx_config.cid,
/*payload_from_controller_fn=*/nullptr,
/*payload_from_host_fn=*/nullptr,
/*event_fn=*/std::move(event_fn)),
rx_config_(rx_config),
tx_config_(tx_config),
channel_number_(channel_number),
rx_credits_(rx_config.credits),
tx_credits_(tx_config.credits),
payload_from_controller_fn_(std::move(payload_from_controller_fn)) {
PW_LOG_INFO(
"btproxy: RfcommChannel ctor - channel_number_: %u, rx_credits_: %u, "
"tx_credits_: %u",
channel_number_,
rx_credits_,
tx_credits_);
}
RfcommChannel::~RfcommChannel() {
// Don't log dtor of moved-from channels.
if (state() != State::kUndefined) {
PW_LOG_INFO("btproxy: RfcommChannel dtor - channel_number_: %u",
channel_number_);
}
}
} // namespace pw::bluetooth::proxy