blob: a139ff26e5efc4c7d91ba28be8b89956c129d3e1 [file] [log] [blame] [edit]
// Copyright 2025 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.
// Tests that apply across all client channels.
#include <vector>
#include "pw_bluetooth_proxy/h4_packet.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_bluetooth_proxy/proxy_host.h"
#include "pw_bluetooth_proxy_private/test_utils.h"
#include "pw_function/function.h"
namespace pw::bluetooth::proxy {
namespace {
// ########## Util
// See BuildOneOfEachChannel
struct OneOfEachChannelParameters {
Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr;
ChannelEventCallback&& event_fn = nullptr;
};
// See BuildOneOfEachChannel
struct OneOfEachChannel {
OneOfEachChannel(BasicL2capChannel&& basic,
L2capCoc&& coc,
RfcommChannel&& rfcomm,
GattNotifyChannel&& gatt)
: basic_{std::move(basic)},
coc_{std::move(coc)},
rfcomm_{std::move(rfcomm)},
gatt_{std::move(gatt)} {}
std::vector<L2capChannel*> AllChannels() {
return std::vector<L2capChannel*>{&basic_, &coc_, &rfcomm_, &gatt_};
}
BasicL2capChannel basic_;
L2capCoc coc_;
RfcommChannel rfcomm_;
GattNotifyChannel gatt_;
};
class ChannelProxyTest : public ProxyHostTest {
protected:
// Builds a struct with one of each channel to support tests across all
// of them.
//
// Note, shared_event_fn is a reference (rather than a rvalue) so it can
// be shared across each channel.
OneOfEachChannel BuildOneOfEachChannel(
ProxyHost& proxy, ChannelEventCallback& shared_event_fn) {
// Each channel its unique cids and its own rvalue lambda which calls the
// shared_event_fn.
return OneOfEachChannel(
BuildBasicL2capChannel(
proxy,
{.local_cid = 201,
.remote_cid = 301,
.event_fn =
[&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}}),
BuildCoc(proxy,
{.local_cid = 202,
.remote_cid = 302,
.event_fn =
[&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}}),
BuildRfcomm(proxy,
{.rx_config{.cid = 203}, .tx_config = {.cid = 303}},
/*receive_fn=*/nullptr,
/*event_fn=*/
[&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}),
BuildGattNotifyChannel(
proxy, {.event_fn = [&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}}));
}
};
// ########## Tests
// Test that each channel type properly send a close event when it is closed
// due to proxy destruction.
// Note BuildOneOfEachChannel (and Build test utils in general) does a move of
// each channel during ctor, so this tests also verifies stop events work after
// a move.
TEST_F(ChannelProxyTest, ChannelsStopOnProxyDestruction) {
pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
[](H4PacketWithHci&&) {});
pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
[](H4PacketWithH4&&) {});
size_t events_received = 0;
pw::Vector<ProxyHost, 1> proxy;
proxy.emplace_back(std::move(send_to_host_fn),
std::move(send_to_controller_fn),
/*le_acl_credits_to_reserve=*/0,
/*br_edr_acl_credits_to_reserve=*/0);
// This event function will be called by each of the channels' event
// functions.
ChannelEventCallback shared_event_fn =
[&events_received](L2capChannelEvent event) {
++events_received;
EXPECT_EQ(event, L2capChannelEvent::kChannelClosedByOther);
};
BasicL2capChannel close_first_channel = BuildBasicL2capChannel(
proxy.front(),
BasicL2capParameters{
.event_fn = [&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}});
OneOfEachChannel channel_struct =
BuildOneOfEachChannel(proxy.front(), shared_event_fn);
// Channel already closed before Proxy destruction should not be affected.
close_first_channel.Close();
EXPECT_EQ(events_received, 1ul);
EXPECT_EQ(close_first_channel.state(), L2capChannel::State::kClosed);
// Proxy dtor should result in close event for each of
// the previously still open channels (and they should now be closed).
proxy.clear();
EXPECT_EQ(events_received, 1 + channel_struct.AllChannels().size());
for (L2capChannel* channel : channel_struct.AllChannels()) {
EXPECT_EQ(channel->state(), L2capChannel::State::kClosed);
}
// And first channel should remain closed of course.
EXPECT_EQ(close_first_channel.state(), L2capChannel::State::kClosed);
}
// Test that each channel type properly send a close event when it is closed
// due to reset.
// Note BuildOneOfEachChannel (and Build test utils in general) does a move of
// each channel during ctor, so this tests also verifies close events work after
// a move.
TEST_F(ChannelProxyTest, ChannelsCloseOnReset) {
pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
[](H4PacketWithHci&&) {});
pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
[](H4PacketWithH4&&) {});
size_t events_received = 0;
ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
std::move(send_to_controller_fn),
/*le_acl_credits_to_reserve=*/0,
/*br_edr_acl_credits_to_reserve=*/0);
// This event function will be called by each of the channels' event
// functions.
ChannelEventCallback shared_event_fn =
[&events_received](L2capChannelEvent event) {
if (++events_received == 1) {
EXPECT_EQ(event, L2capChannelEvent::kChannelClosedByOther);
} else {
EXPECT_EQ(event, L2capChannelEvent::kReset);
}
};
BasicL2capChannel close_first_channel = BuildBasicL2capChannel(
proxy,
BasicL2capParameters{
.event_fn = [&shared_event_fn](L2capChannelEvent event) {
shared_event_fn(event);
}});
// BuildOneOfEachChannel does a move of each channel, so we are testing them
// after a move.
OneOfEachChannel channel_struct =
BuildOneOfEachChannel(proxy, shared_event_fn);
// Channel already closed before Proxy reset should not be affected.
close_first_channel.Close();
EXPECT_EQ(events_received, 1ul);
EXPECT_EQ(close_first_channel.state(), L2capChannel::State::kClosed);
// Proxy reset should result in close event for each of
// the previously still open channels (and they should now be closed).
proxy.Reset();
EXPECT_EQ(events_received, 1 + channel_struct.AllChannels().size());
for (L2capChannel* channel : channel_struct.AllChannels()) {
EXPECT_EQ(channel->state(), L2capChannel::State::kClosed);
}
// And first channel should remain closed of course.
EXPECT_EQ(close_first_channel.state(), L2capChannel::State::kClosed);
}
} // namespace
} // namespace pw::bluetooth::proxy