-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pw_bluetooth_proxy: L2CAP CoC & GATT Notify channels
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
Showing
16 changed files
with
1,243 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.