blob: 0f88ed9963665f747658a09c4eb06ed6116c46c5 [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/basic_l2cap_channel.h"
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_log/log.h"
#include "pw_status/try.h"
namespace pw::bluetooth::proxy {
pw::Result<BasicL2capChannel> BasicL2capChannel::Create(
L2capChannelManager& l2cap_channel_manager,
multibuf::MultiBufAllocator* rx_multibuf_allocator,
uint16_t connection_handle,
AclTransportType transport,
uint16_t local_cid,
uint16_t remote_cid,
OptionalPayloadReceiveCallback&& payload_from_controller_fn,
OptionalPayloadReceiveCallback&& payload_from_host_fn,
ChannelEventCallback&& event_fn) {
if (!AreValidParameters(/*connection_handle=*/connection_handle,
/*local_cid=*/local_cid,
/*remote_cid=*/remote_cid)) {
return pw::Status::InvalidArgument();
}
return BasicL2capChannel(
l2cap_channel_manager,
rx_multibuf_allocator,
/*connection_handle=*/connection_handle,
/*transport=*/transport,
/*local_cid=*/local_cid,
/*remote_cid=*/remote_cid,
/*payload_from_controller_fn=*/std::move(payload_from_controller_fn),
/*payload_from_host_fn=*/std::move(payload_from_host_fn),
/*event_fn=*/std::move(event_fn));
}
StatusWithMultiBuf BasicL2capChannel::Write(multibuf::MultiBuf&& payload) {
if (!IsOkL2capDataLength(payload.size())) {
PW_LOG_WARN("Payload (%zu bytes) is too large. So will not process.",
payload.size());
return {Status::InvalidArgument(), std::move(payload)};
}
return L2capChannel::Write(std::move(payload));
}
std::optional<H4PacketWithH4> BasicL2capChannel::GenerateNextTxPacket() {
if (state() != State::kRunning || PayloadQueueEmpty()) {
return std::nullopt;
}
ConstByteSpan payload = GetFrontPayloadSpan();
pw::Result<H4PacketWithH4> result =
PopulateTxL2capPacket(payload.size_bytes());
if (!result.ok()) {
return std::nullopt;
}
H4PacketWithH4 h4_packet = std::move(result.value());
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());
PW_CHECK(TryToCopyToEmbossStruct(bframe.payload(), payload));
PW_CHECK(acl.Ok());
PW_CHECK(bframe.Ok());
return h4_packet;
}
BasicL2capChannel::BasicL2capChannel(
L2capChannelManager& l2cap_channel_manager,
multibuf::MultiBufAllocator* rx_multibuf_allocator,
uint16_t connection_handle,
AclTransportType transport,
uint16_t local_cid,
uint16_t remote_cid,
OptionalPayloadReceiveCallback&& payload_from_controller_fn,
OptionalPayloadReceiveCallback&& payload_from_host_fn,
ChannelEventCallback&& event_fn)
: L2capChannel(
l2cap_channel_manager,
rx_multibuf_allocator,
/*connection_handle=*/connection_handle,
/*transport=*/transport,
/*local_cid=*/local_cid,
/*remote_cid=*/remote_cid,
/*payload_from_controller_fn=*/std::move(payload_from_controller_fn),
/*payload_from_host_fn=*/std::move(payload_from_host_fn),
/*event_fn=*/std::move(event_fn)) {
PW_LOG_INFO("btproxy: BasicL2capChannel ctor");
}
BasicL2capChannel::~BasicL2capChannel() {
// Don't log dtor of moved-from channels.
if (state() != State::kUndefined) {
PW_LOG_INFO("btproxy: BasicL2capChannel dtor");
}
}
bool BasicL2capChannel::DoHandlePduFromController(pw::span<uint8_t> bframe) {
Result<emboss::BFrameWriter> bframe_view =
MakeEmbossWriter<emboss::BFrameWriter>(bframe);
if (!bframe_view.ok()) {
// TODO: https://pwbug.dev/360929142 - Stop channel on error.
PW_LOG_ERROR("(CID: 0x%X) Received invalid B-frame. So will drop.",
local_cid());
return true;
}
return SendPayloadFromControllerToClient(
span(bframe_view->payload().BackingStorage().data(),
bframe_view->payload().SizeInBytes()));
}
bool BasicL2capChannel::HandlePduFromHost(pw::span<uint8_t> bframe) {
Result<emboss::BFrameWriter> bframe_view =
MakeEmbossWriter<emboss::BFrameWriter>(bframe);
if (!bframe_view.ok()) {
// TODO: https://pwbug.dev/360929142 - Stop channel on error.
PW_LOG_ERROR("(CID: 0x%X) Host transmitted invalid B-frame. So will drop.",
local_cid());
return true;
}
return SendPayloadFromHostToClient(
span(bframe_view->payload().BackingStorage().data(),
bframe_view->payload().SizeInBytes()));
}
} // namespace pw::bluetooth::proxy