blob: 80e146626a3c2b06139616fdc99d7d06d67ef901 [file] [log] [blame]
// 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 <cstdint>
#include <numeric>
#include "emboss_util.h"
#include "lib/stdcompat/utility.h"
#include "pw_bluetooth/hci_commands.emb.h"
#include "pw_bluetooth/hci_common.emb.h"
#include "pw_bluetooth/hci_events.emb.h"
#include "pw_bluetooth/hci_h4.emb.h"
#include "pw_bluetooth_proxy/hci_proxy.h"
#include "pw_bluetooth_proxy/passthrough_policy.h"
#include "pw_unit_test/framework.h" // IWYU pragma: keep
namespace pw::bluetooth::proxy {
namespace {
// Util functions
// Simple struct for returning a H4 array and an emboss view on its HCI packet.
template <typename EmbossT>
struct EmbossViewWithH4Buffer {
// Size is +1 to accomidate the H4 packet type at the front.
std::array<uint8_t, EmbossT::SizeInBytes() + 1> arr;
EmbossT view;
};
// Return H4 command buffer and Emboss view using indicated Emboss type to send
// towards controller.
template <typename EmbossT>
EmbossViewWithH4Buffer<EmbossT> CreateToControllerBuffer(
emboss::OpCode opcode) {
EmbossViewWithH4Buffer<EmbossT> view_arr;
std::iota(view_arr.arr.begin(), view_arr.arr.end(), 100);
view_arr.arr[0] = cpp23::to_underlying(emboss::H4PacketType::COMMAND);
view_arr.view = MakeEmboss<EmbossT>(H4HciSubspan(view_arr.arr));
EXPECT_TRUE(view_arr.view.IsComplete());
view_arr.view.header().opcode_full().Write(opcode);
return view_arr;
}
// Populate an H4 buffer to send towards controller. Type should be one
// that none of our policies try to interact with.
std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1>
CreateNoninteractingToControllerBuffer() {
EmbossViewWithH4Buffer<emboss::InquiryCommandWriter> view_arr =
CreateToControllerBuffer<emboss::InquiryCommandWriter>(
emboss::OpCode::LINK_KEY_REQUEST_REPLY);
return view_arr.arr;
}
// Return H4 event buffer and Emboss view using indicated Emboss type to send
// towards host.
template <typename EmbossT>
EmbossViewWithH4Buffer<EmbossT> CreateToHostBuffer(
uint event_code, emboss::StatusCode status_code) {
EmbossViewWithH4Buffer<EmbossT> view_arr;
std::iota(view_arr.arr.begin(), view_arr.arr.end(), 100);
view_arr.arr[0] = cpp23::to_underlying(emboss::H4PacketType::EVENT);
view_arr.view = MakeEmboss<EmbossT>(H4HciSubspan(view_arr.arr));
view_arr.view.header().event_code().Write(event_code);
view_arr.view.status().Write(status_code);
EXPECT_TRUE(view_arr.view.IsComplete());
return view_arr;
}
// Populate an H4 buffer to send towards host. Type should be one
// that none of our policies try to interact with.
std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
CreateNonInteractingToHostBuffer() {
EmbossViewWithH4Buffer<emboss::InquiryCompleteEventWriter> view_arr =
CreateToHostBuffer<emboss::InquiryCompleteEventWriter>(
0x01, emboss::StatusCode::COMMAND_DISALLOWED);
return view_arr.arr;
}
// Tests
// Example for docs.rst.
TEST(Example, ExampleUsage) {
// Populate H4 buffer to send towards controller.
std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1>
h4_array_from_host = CreateNoninteractingToControllerBuffer();
auto h4_span_from_host = pw::span(h4_array_from_host);
// Populate H4 buffer to send towards host.
std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
h4_array_from_controller = CreateNonInteractingToHostBuffer();
auto h4_span_from_controller = pw::span(h4_array_from_controller);
H4HciPacketSendFn containerSendToHostFn([](H4HciPacket packet) {});
H4HciPacketSendFn containerSendToControllerFn(([](H4HciPacket packet) {}));
// DOCSTAG: [pw_bluetooth_proxy-examples-basic]
#include "pw_bluetooth_proxy/hci_proxy.h"
#include "pw_bluetooth_proxy/passthrough_policy.h"
// Container creates proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy{};
std::array<ProxyPolicy*, 1> policies{&passthrough_policy};
HciProxy proxy = HciProxy(std::move(containerSendToHostFn),
std::move(containerSendToControllerFn),
policies);
// Container passes H4 packets from host through proxy. Proxy will in turn
// call the container-provided `containerSendToControllerFn` to pass them on
// to the controller. Note depending on the policies, some packets may be
// modified, added, or removed.
proxy.ProcessH4HciFromHost(h4_span_from_host);
// Container passes H4 packets from controller through proxy. Proxy will in
// turn call the container-provided `containerSendToHostFn` to pass them
// on to the controller. Note depending on the policies, some packets may be
// modified, added, or removed.
proxy.ProcessH4HciFromController(h4_span_from_controller);
// DOCSTAG: [pw_bluetooth_proxy-examples-basic]
}
// Verify buffer is properly passed (is equal value) when we have no policies.
TEST(PassthroughTest, WithNoPoliciesTheToControllerPassesEqualBuffer) {
// Populate H4 buffer to send towards controller.
std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> h4_array =
CreateNoninteractingToControllerBuffer();
// Outbound callbacks where we verify packet is equal (just testing to
// controller in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy{};
std::array<ProxyPolicy*, 0> policies{};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromHost(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
TEST(PassthroughTest, WithOnePolicyTheToControllerPassesEqualBuffer) {
// Populate H4 buffer to send towards controller.
std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> h4_array =
CreateNoninteractingToControllerBuffer();
// Outbound callbacks where we verify packet is equal (just testing to
// controller in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy{};
std::array<ProxyPolicy*, 1> policies{&passthrough_policy};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromHost(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
TEST(PassthroughTest, WithThreePoliciesTheToControllerPassesEqualBuffer) {
// Populate H4 buffer to send towards controller.
std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> h4_array =
CreateNoninteractingToControllerBuffer();
// Outbound callbacks where we verify packet is equal (just testing to
// controller in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy1{};
PassthroughPolicy passthrough_policy2{};
PassthroughPolicy passthrough_policy3{};
std::array<ProxyPolicy*, 3> policies{
&passthrough_policy1, &passthrough_policy2, &passthrough_policy3};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromHost(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
// Verify buffer is passed to host callback with the same contents with no
// policies.
TEST(PassthroughTest, WithNoPoliciesTheToHostPassesEqualBuffer) {
// Populate H4 buffer to send towards host.
std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
h4_array = CreateNonInteractingToHostBuffer();
// Outbound callbacks where we verify packet is equal (just testing to host
// in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_controller_fn(
[]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
std::array<ProxyPolicy*, 0> policies{};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromController(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
TEST(PassthroughTest, WithOnePolicyTheToHostPassesEqualBuffer) {
// Populate H4 buffer to send towards host.
std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
h4_array = CreateNonInteractingToHostBuffer();
// Outbound callbacks where we verify packet is equal (just testing to host
// in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_controller_fn(
[]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy{};
std::array<ProxyPolicy*, 1> policies{&passthrough_policy};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromController(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
TEST(PassthroughTest, WithThreePoliciesTheToHostPassesEqualBuffer) {
// Populate H4 buffer to send towards host.
std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
h4_array = CreateNonInteractingToHostBuffer();
// Outbound callbacks where we verify packet is equal (just testing to host
// in this test).
bool send_called = false;
auto send_capture = std::pair(&send_called, &h4_array);
H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
*(send_capture.first) = true;
EXPECT_TRUE(std::equal(packet.begin(),
packet.end(),
send_capture.second->begin(),
send_capture.second->end()));
});
H4HciPacketSendFn send_to_controller_fn(
[]([[maybe_unused]] H4HciPacket packet) {});
// Create proxy with simple passthrough policy.
PassthroughPolicy passthrough_policy1{};
PassthroughPolicy passthrough_policy2{};
PassthroughPolicy passthrough_policy3{};
std::array<ProxyPolicy*, 3> policies{
&passthrough_policy1, &passthrough_policy2, &passthrough_policy3};
HciProxy proxy = HciProxy(
std::move(send_to_host_fn), std::move(send_to_controller_fn), policies);
proxy.ProcessH4HciFromController(pw::span(h4_array));
// Verify to controller callback was called.
EXPECT_EQ(send_called, true);
}
} // namespace
} // namespace pw::bluetooth::proxy