Skip to content

Commit

Permalink
pw_bluetooth_proxy: L2CAP CoC & GATT Notify channels
Browse files Browse the repository at this point in the history
Add L2CAP CoC & GATT Notify channel abstractions. Move H4 packet
storage into its own class. Migrate ProxyHost to use GATT Notify
channel API in SendGattNotify(...).

Bug: 360929142
Change-Id: I50590f603682f95b5b99106c886984bbd52d15b6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/230832
Pigweed-Auto-Submit: Austin Foxley <[email protected]>
Docs-Not-Needed: Ali Saeed <[email protected]>
Lint: Lint 🤖 <[email protected]>
Commit-Queue: Auto-Submit <[email protected]>
Presubmit-Verified: CQ Bot Account <[email protected]>
Reviewed-by: Ben Lawson <[email protected]>
  • Loading branch information
acsaeed authored and CQ Bot Account committed Nov 4, 2024
1 parent 6329e8d commit 29771c1
Show file tree
Hide file tree
Showing 16 changed files with 1,243 additions and 241 deletions.
11 changes: 11 additions & 0 deletions pw_bluetooth_proxy/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,23 @@ cc_library(
name = "pw_bluetooth_proxy",
srcs = [
"acl_data_channel.cc",
"gatt_notify_channel.cc",
"h4_storage.cc",
"l2cap_coc.cc",
"l2cap_write_channel.cc",
"proxy_host.cc",
],
hdrs = [
"public/pw_bluetooth_proxy/gatt_notify_channel.h",
"public/pw_bluetooth_proxy/h4_packet.h",
"public/pw_bluetooth_proxy/internal/acl_data_channel.h",
"public/pw_bluetooth_proxy/internal/emboss_util.h",
"public/pw_bluetooth_proxy/internal/gatt_notify_channel_internal.h",
"public/pw_bluetooth_proxy/internal/h4_storage.h",
"public/pw_bluetooth_proxy/internal/hci_transport.h",
"public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h",
"public/pw_bluetooth_proxy/internal/l2cap_write_channel.h",
"public/pw_bluetooth_proxy/l2cap_coc.h",
"public/pw_bluetooth_proxy/proxy_host.h",
],
includes = ["public"],
Expand All @@ -42,6 +52,7 @@ cc_library(
"//pw_containers",
"//pw_function",
"//pw_log",
"//pw_result",
"//pw_span",
"//pw_status",
"//pw_sync:lock_annotations",
Expand Down
11 changes: 11 additions & 0 deletions pw_bluetooth_proxy/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ pw_test_group("tests") {
pw_source_set("pw_bluetooth_proxy") {
public_configs = [ ":public_include_path" ]
public = [
"public/pw_bluetooth_proxy/gatt_notify_channel.h",
"public/pw_bluetooth_proxy/h4_packet.h",
"public/pw_bluetooth_proxy/internal/acl_data_channel.h",
"public/pw_bluetooth_proxy/internal/emboss_util.h",
"public/pw_bluetooth_proxy/internal/gatt_notify_channel_internal.h",
"public/pw_bluetooth_proxy/internal/h4_storage.h",
"public/pw_bluetooth_proxy/internal/hci_transport.h",
"public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h",
"public/pw_bluetooth_proxy/internal/l2cap_write_channel.h",
"public/pw_bluetooth_proxy/l2cap_coc.h",
"public/pw_bluetooth_proxy/proxy_host.h",
]
public_deps = [
Expand All @@ -61,12 +67,17 @@ pw_source_set("pw_bluetooth_proxy") {
dir_pw_containers,
dir_pw_function,
dir_pw_log,
dir_pw_result,
dir_pw_span,
dir_pw_status,
]
deps = [ dir_pw_log ]
sources = [
"acl_data_channel.cc",
"gatt_notify_channel.cc",
"h4_storage.cc",
"l2cap_coc.cc",
"l2cap_write_channel.cc",
"proxy_host.cc",
]
}
Expand Down
11 changes: 11 additions & 0 deletions pw_bluetooth_proxy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ endif()

pw_add_library(pw_bluetooth_proxy STATIC
HEADERS
public/pw_bluetooth_proxy/gatt_notify_channel.h
public/pw_bluetooth_proxy/h4_packet.h
public/pw_bluetooth_proxy/internal/acl_data_channel.h
public/pw_bluetooth_proxy/internal/emboss_util.h
public/pw_bluetooth_proxy/internal/gatt_notify_channel_internal.h
public/pw_bluetooth_proxy/internal/h4_storage.h
public/pw_bluetooth_proxy/internal/hci_transport.h
public/pw_bluetooth_proxy/internal/l2cap_coc_internal.h
public/pw_bluetooth_proxy/internal/l2cap_write_channel.h
public/pw_bluetooth_proxy/l2cap_coc.h
public/pw_bluetooth_proxy/proxy_host.h
PUBLIC_INCLUDES
public
Expand All @@ -42,12 +48,17 @@ pw_add_library(pw_bluetooth_proxy STATIC
pw_containers
pw_function
pw_log
pw_result
pw_span
pw_status
pw_sync.lock_annotations
pw_sync.mutex
SOURCES
acl_data_channel.cc
gatt_notify_channel.cc
h4_storage.cc
l2cap_coc.cc
l2cap_write_channel.cc
proxy_host.cc
)

Expand Down
97 changes: 97 additions & 0 deletions pw_bluetooth_proxy/gatt_notify_channel.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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/gatt_notify_channel.h"

#include "pw_bluetooth/att.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_log/log.h"

namespace pw::bluetooth::proxy {

pw::Status GattNotifyChannel::Write(pw::span<const uint8_t> attribute_value) {
constexpr uint16_t kMaxAttributeSize =
H4Storage::GetH4BuffSize() - sizeof(emboss::H4PacketType) -
emboss::AclDataFrameHeader::IntrinsicSizeInBytes() -
emboss::BasicL2capHeader::IntrinsicSizeInBytes() -
emboss::AttHandleValueNtf::MinSizeInBytes();
if (attribute_value.size() > kMaxAttributeSize) {
PW_LOG_ERROR("Attribute too large (%zu > %d). So will not process.",
attribute_value.size(),
kMaxAttributeSize);
return pw::Status::InvalidArgument();
}

size_t att_size =
emboss::AttHandleValueNtf::MinSizeInBytes() + attribute_value.size();
pw::Result<H4PacketWithH4> h4_result = PopulateTxL2capPacket(att_size);
if (!h4_result.ok()) {
// This can fail as a result of the L2CAP PDU not fitting in an H4 buffer
// or if all buffers are occupied.
// TODO: https://pwbug.dev/365179076 - Once we support ACL fragmentation,
// this function will not fail due to the L2CAP PDU size not fitting.
return h4_result.status();
}
H4PacketWithH4 h4_packet = std::move(*h4_result);

// Write ATT PDU.
emboss::AclDataFrameWriter acl =
MakeEmboss<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
emboss::BFrameWriter l2cap =
emboss::MakeBFrameView(acl.payload().BackingStorage().data(),
acl.payload().BackingStorage().SizeInBytes());
emboss::AttHandleValueNtfWriter att_notify =
emboss::MakeAttHandleValueNtfView(attribute_value.size(),
l2cap.payload().BackingStorage().data(),
att_size);
att_notify.attribute_opcode().Write(emboss::AttOpcode::ATT_HANDLE_VALUE_NTF);
att_notify.attribute_handle().Write(attribute_handle_);
std::memcpy(att_notify.attribute_value().BackingStorage().data(),
attribute_value.data(),
attribute_value.size());

// H4 packet is hereby moved. Either ACL data channel will move packet to
// controller or will be unable to send packet. In either case, packet will be
// destructed, so its release function will be invoked.
return SendL2capPacket(std::move(h4_packet));
}

pw::Result<GattNotifyChannel> GattNotifyChannel::Create(
AclDataChannel& acl_data_channel,
H4Storage& h4_storage,
uint16_t connection_handle,
uint16_t attribute_handle) {
if (!L2capWriteChannel::AreValidParameters(connection_handle,
kAttributeProtocolCID)) {
return pw::Status::InvalidArgument();
}
if (attribute_handle == 0) {
PW_LOG_ERROR("Attribute handle cannot be 0.");
return pw::Status::InvalidArgument();
}
return GattNotifyChannel(
acl_data_channel, h4_storage, connection_handle, attribute_handle);
}

GattNotifyChannel::GattNotifyChannel(AclDataChannel& acl_data_channel,
H4Storage& h4_storage,
uint16_t connection_handle,
uint16_t attribute_handle)
: L2capWriteChannel(acl_data_channel,
h4_storage,
connection_handle,
kAttributeProtocolCID),
attribute_handle_(attribute_handle) {}

} // namespace pw::bluetooth::proxy
64 changes: 64 additions & 0 deletions pw_bluetooth_proxy/h4_storage.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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/internal/h4_storage.h"

#include "pw_assert/check.h" // IWYU pragma: keep

namespace pw::bluetooth::proxy {

std::array<containers::Pair<uint8_t*, bool>, H4Storage::kNumH4Buffs>
H4Storage::InitOccupiedMap() {
storage_mutex_.lock();
std::array<containers::Pair<uint8_t*, bool>, kNumH4Buffs> arr;
for (size_t i = 0; i < kNumH4Buffs; ++i) {
arr[i] = {h4_buffs_[i].data(), false};
}
storage_mutex_.unlock();
return arr;
}

H4Storage::H4Storage() : h4_buff_occupied_(InitOccupiedMap()) {}

std::optional<pw::span<uint8_t>> H4Storage::ReserveH4Buff() {
storage_mutex_.lock();
for (const auto& [buff, occupied] : h4_buff_occupied_) {
if (!occupied) {
h4_buff_occupied_.at(buff) = true;
storage_mutex_.unlock();
return {{buff, kH4BuffSize}};
}
}
storage_mutex_.unlock();
return std::nullopt;
}

void H4Storage::ReleaseH4Buff(const uint8_t* buffer) {
storage_mutex_.lock();
PW_CHECK(h4_buff_occupied_.contains(const_cast<uint8_t*>(buffer)),
"Received release callback for invalid buffer address.");

h4_buff_occupied_.at(const_cast<uint8_t*>(buffer)) = false;
storage_mutex_.unlock();
}

void H4Storage::Reset() {
storage_mutex_.lock();
for (const auto& [buff, _] : h4_buff_occupied_) {
h4_buff_occupied_.at(buff) = false;
}
storage_mutex_.unlock();
}

} // namespace pw::bluetooth::proxy
126 changes: 126 additions & 0 deletions pw_bluetooth_proxy/l2cap_coc.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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/l2cap_coc.h"

#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/internal/l2cap_write_channel.h"
#include "pw_log/log.h"

namespace pw::bluetooth::proxy {

pw::Status L2capCoc::Stop() {
if (state_ == CocState::kStopped) {
return Status::InvalidArgument();
}
state_ = CocState::kStopped;
return OkStatus();
}

pw::Status L2capCoc::Write(pw::span<const uint8_t> payload) {
if (state_ == CocState::kStopped) {
return Status::FailedPrecondition();
}

if (payload.size() > tx_mtu_) {
PW_LOG_ERROR(
"Payload (%zu bytes) exceeds MTU (%d bytes). So will not process.",
payload.size(),
tx_mtu_);
return pw::Status::InvalidArgument();
}
// We do not currently support segmentation, so the payload is required to fit
// within the remote peer's Maximum PDU payload Size.
// TODO: https://pwbug.dev/360932103 - Support packet segmentation.
if (payload.size() > tx_mps_) {
PW_LOG_ERROR(
"Payload (%zu bytes) exceeds MPS (%d bytes). So will not process.",
payload.size(),
tx_mps_);
return pw::Status::InvalidArgument();
}

// 2 bytes for SDU length field
size_t l2cap_data_length = payload.size() + 2;
pw::Result<H4PacketWithH4> h4_result =
PopulateTxL2capPacket(l2cap_data_length);
if (!h4_result.ok()) {
// This can fail as a result of the L2CAP PDU not fitting in an H4 buffer
// or if all buffers are occupied.
// TODO: https://pwbug.dev/365179076 - Once we support ACL fragmentation,
// this function will not fail due to the L2CAP PDU size not fitting.
return h4_result.status();
}
H4PacketWithH4 h4_packet = std::move(*h4_result);

emboss::AclDataFrameWriter acl =
MakeEmboss<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
// Write payload.
emboss::FirstKFrameWriter kframe =
emboss::MakeFirstKFrameView(acl.payload().BackingStorage().data(),
acl.payload().BackingStorage().SizeInBytes());
kframe.sdu_length().Write(payload.size());
std::memcpy(
kframe.payload().BackingStorage().data(), payload.data(), payload.size());

// H4 packet is hereby moved. Either ACL data channel will move packet to
// controller or will be unable to send packet. In either case, packet will be
// destructed, so its release function will be invoked.
return SendL2capPacket(std::move(h4_packet));
}

pw::Result<L2capCoc> L2capCoc::Create(
AclDataChannel& acl_data_channel,
H4Storage& h4_storage,
uint16_t connection_handle,
CocConfig rx_config,
CocConfig tx_config,
pw::Function<void(Event event)>&& event_fn) {
if (!L2capWriteChannel::AreValidParameters(connection_handle,
tx_config.cid)) {
return pw::Status::InvalidArgument();
}

if (tx_config.mps < emboss::L2capLeCreditBasedConnectionReq::min_mps() ||
tx_config.mps > emboss::L2capLeCreditBasedConnectionReq::max_mps()) {
PW_LOG_ERROR(
"Tx MPS (%d octets) invalid. L2CAP implementations shall support a "
"minimum MPS of 23 octets and may support an MPS up to 65533 octets.",
tx_config.mps);
return pw::Status::InvalidArgument();
}

return L2capCoc(acl_data_channel,
h4_storage,
connection_handle,
rx_config,
tx_config,
std::move(event_fn));
}

L2capCoc::L2capCoc(AclDataChannel& acl_data_channel,
H4Storage& h4_storage,
uint16_t connection_handle,
[[maybe_unused]] CocConfig rx_config,
CocConfig tx_config,
pw::Function<void(Event event)>&& event_fn)
: L2capWriteChannel(
acl_data_channel, h4_storage, connection_handle, tx_config.cid),
state_(CocState::kRunning),
tx_mtu_(tx_config.mtu),
tx_mps_(tx_config.mps),
event_fn_(std::move(event_fn)) {}

} // namespace pw::bluetooth::proxy
Loading

0 comments on commit 29771c1

Please sign in to comment.