blob: 8a3491af700db9f991a70a7f7d3191b677e4ceb1 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/hci/sequential_command_runner.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/mock_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h"
namespace bt::hci {
namespace {
constexpr hci_spec::OpCode kTestOpCode = 0xFFFF;
constexpr hci_spec::OpCode kTestOpCode2 = 0xF00F;
using bt::testing::CommandTransaction;
using TestingBase = bt::testing::ControllerTest<bt::testing::MockController>;
class SequentialCommandRunnerTest : public TestingBase {
public:
SequentialCommandRunnerTest() = default;
~SequentialCommandRunnerTest() override = default;
};
using HCI_SequentialCommandRunnerTest = SequentialCommandRunnerTest;
TEST_F(SequentialCommandRunnerTest, SequentialCommandRunner) {
// HCI command with custom opcode FFFF.
StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);
StaticByteBuffer command_status_error_bytes(hci_spec::kCommandStatusEventCode,
0x04, // parameter_total_size (4 byte payload)
hci_spec::StatusCode::kHardwareFailure, 1, 0xFF,
0xFF);
StaticByteBuffer command_cmpl_error_bytes(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
1, 0xFF, 0xFF, hci_spec::StatusCode::kReserved0);
auto command_cmpl_success_bytes = StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
1, 0xFF, 0xFF, hci_spec::StatusCode::kSuccess);
// Here we perform multiple test sequences where we queue up several commands
// in each sequence. We expect each sequence to terminate differently after
// the following HCI transactions:
//
// Sequence 1 (HCI packets)
// -> Command; <- error status
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_status_error_bytes);
// Sequence 2 (HCI packets)
// -> Command; <- error complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_error_bytes);
// Sequence 3 (HCI packets)
// -> Command; <- success complete
// -> Command; <- error complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_error_bytes);
// Sequence 4 (HCI packets)
// -> Command; <- success complete
// -> Command; <- success complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
// Sequence 5 (HCI packets)
// -> Command; <- success complete
// -> Command; <- success complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
// Sequence 1 (test)
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(1, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(hci_spec::StatusCode::kHardwareFailure), status);
cb_called = 0;
// Sequence 2 (test)
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(1, cb_called);
EXPECT_EQ(2, status_cb_called);
EXPECT_EQ(ToResult(hci_spec::StatusCode::kReserved0), status);
cb_called = 0;
// Sequence 3 (test)
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(2, cb_called);
EXPECT_EQ(3, status_cb_called);
EXPECT_EQ(ToResult(hci_spec::StatusCode::kReserved0), status);
cb_called = 0;
// Sequence 4 (test)
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(2, cb_called);
EXPECT_EQ(4, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
cb_called = 0;
status_cb_called = 0;
// Sequence 5 (test) (no callback passed to QueueCommand)
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode));
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode));
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(0, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
}
TEST_F(SequentialCommandRunnerTest, SequentialCommandRunnerCancel) {
StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);
auto command_cmpl_error_bytes =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
1, 0xFF, 0xFF, hci_spec::StatusCode::kHardwareFailure);
auto command_cmpl_success_bytes = StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
1, 0xFF, 0xFF, hci_spec::StatusCode::kSuccess);
// Sequence 1
// -> Command; <- success complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
// Sequence 2
// -> Command; <- success complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
// Sequence 3
// -> Command; <- success complete
// -> Command; <- error complete
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_error_bytes);
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
// Sequence 1: Sequence will be cancelled after the first command.
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
// Call RunCommands() and cancel the sequence immediately. The
// first command will go out but no successive packets should be sent.
// The status callback should be invoked
// No command callbacks should be called.
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
cmd_runner.Cancel();
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(0, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(HostError::kCanceled), status);
cb_called = 0;
status_cb_called = 0;
status = fitx::ok();
// Sequence 2: Sequence will be cancelled after first command. This tests
// canceling a sequence from a CommandCompleteCallback.
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), [&](const EventPacket& event) {
bt_log(TRACE, "hci-test", "callback called");
cmd_runner.Cancel();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
});
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
// |status_cb| is expected to get called with kCanceled
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(0, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(HostError::kCanceled), status);
cb_called = 0;
status_cb_called = 0;
status = fitx::ok();
// Sequence 3: Sequence will be cancelled after first command and immediately
// followed by a second command which will fail. This tests canceling a
// sequence and initiating a new one from a CommandCompleteCallback.
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), [&](const EventPacket& event) {
cmd_runner.Cancel();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(HostError::kCanceled), status);
// Queue multiple commands (only one will execute since MockController will send back an error
// status).
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
cmd_runner.RunCommands(status_cb);
});
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb); // <-- Should not run
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
// The cb queued from inside the first callback should have been called.
EXPECT_EQ(1, cb_called);
// The result callback should have been called with the failure result.
EXPECT_EQ(2, status_cb_called);
EXPECT_EQ(ToResult(hci_spec::StatusCode::kHardwareFailure), status);
}
TEST_F(SequentialCommandRunnerTest, ParallelCommands) {
// Need to signal to the queue that we can run more than one command at once.
auto command_status_queue_increase =
StaticByteBuffer(hci_spec::kCommandStatusEventCode,
0x04, // parameter_total_size (4 byte payload)
hci_spec::StatusCode::kSuccess, 250, 0x00, 0x00);
// HCI command with custom opcode FFFF.
StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);
auto command_status_error_bytes =
StaticByteBuffer(hci_spec::kCommandStatusEventCode,
0x04, // parameter_total_size (4 byte payload)
hci_spec::StatusCode::kHardwareFailure, 2, 0xFF, 0xFF);
auto command_cmpl_error_bytes = StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
2, 0xFF, 0xFF, hci_spec::StatusCode::kReserved0);
auto command_cmpl_success_bytes = StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
2, 0xFF, 0xFF, hci_spec::StatusCode::kSuccess);
// HCI command with custom opcode F00F.
StaticByteBuffer command2_bytes(0x0F, 0xF0, 0x00);
auto command2_status_error_bytes =
StaticByteBuffer(hci_spec::kCommandStatusEventCode,
0x04, // parameter_total_size (4 byte payload)
hci_spec::StatusCode::kHardwareFailure, 2, 0x0F, 0xF0);
auto command2_cmpl_error_bytes =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
2, 0x0F, 0xF0, hci_spec::StatusCode::kReserved0);
auto command2_cmpl_success_bytes =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x04, // parameter_total_size (4 byte payload)
2, 0x0F, 0xF0, hci_spec::StatusCode::kSuccess);
StartTestDevice();
test_device()->SendCommandChannelPacket(command_status_queue_increase);
// Parallel commands should all run before commands that require success.
// command and command2 are answered in opposite order because they should be
// sent simultaneously.
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, );
EXPECT_CMD_PACKET_OUT(test_device(), command2_bytes, );
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, &command_cmpl_success_bytes);
int cb_called = 0;
auto cb = [&](const auto&) { cb_called++; };
int status_cb_called = 0;
Result<> status = ToResult(HostError::kFailed);
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode2), cb, /*wait=*/false);
cmd_runner.QueueCommand(
CommandPacket::New(kTestOpCode),
[&](const auto&) {
EXPECT_EQ(2, cb_called);
cb_called++;
},
/*wait=*/true);
// We can also queue to the end of the queue without the last one being a
// wait.
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
// The first two commands should have been sent but no responses are back yet.
EXPECT_EQ(0, cb_called);
// It should not matter if they get answered in opposite order.
test_device()->SendCommandChannelPacket(command2_cmpl_success_bytes);
test_device()->SendCommandChannelPacket(command_cmpl_success_bytes);
RunLoopUntilIdle();
EXPECT_EQ(5, cb_called);
EXPECT_EQ(fitx::ok(), status);
EXPECT_EQ(1, status_cb_called);
cb_called = 0;
status_cb_called = 0;
// If any simultaneous commands fail, the sequence fails and the command
// sequence is terminated.
EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, );
EXPECT_CMD_PACKET_OUT(test_device(), command2_bytes, );
int cb_0_called = 0;
auto cb_0 = [&](const auto&) { cb_0_called++; };
int cb_1_called = 0;
auto cb_1 = [&](const auto&) { cb_1_called++; };
int cb_2_called = 0;
auto cb_2 = [&](const auto&) { cb_2_called++; };
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb_0, /*wait=*/false);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode2), cb_1, /*wait=*/false);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
cb_2); // shouldn't run
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
// The first two commands should have been sent but no responses are back yet.
EXPECT_EQ(0, cb_0_called);
EXPECT_EQ(0, cb_1_called);
EXPECT_EQ(0, cb_2_called);
test_device()->SendCommandChannelPacket(command_status_error_bytes);
test_device()->SendCommandChannelPacket(command2_cmpl_success_bytes);
RunLoopUntilIdle();
// Only the first command's callback should be called, as further callbacks will be canceled due
// to the error status.
EXPECT_EQ(1, cb_0_called);
EXPECT_EQ(0, cb_1_called);
EXPECT_EQ(0, cb_2_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(hci_spec::StatusCode::kHardwareFailure), status);
}
TEST_F(SequentialCommandRunnerTest, CommandCompletesOnStatusEvent) {
auto command = bt::testing::EmptyCommandPacket(kTestOpCode);
auto command0_status_event =
bt::testing::CommandStatusPacket(kTestOpCode, hci_spec::StatusCode::kSuccess);
auto command1 = bt::testing::EmptyCommandPacket(kTestOpCode2);
auto command1_cmpl_event =
bt::testing::CommandCompletePacket(kTestOpCode2, hci_spec::StatusCode::kSuccess);
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false,
hci_spec::kCommandStatusEventCode);
EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_cmpl_event);
cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode2), cb, /*wait=*/true);
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(2, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
}
TEST_F(SequentialCommandRunnerTest, AsyncCommands) {
auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
auto command0_status_event = bt::testing::CommandStatusPacket(hci_spec::kRemoteNameRequest,
hci_spec::StatusCode::kSuccess);
auto command0_cmpl_event = bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());
auto command1 = bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
auto command1_status_event = bt::testing::CommandStatusPacket(hci_spec::kLEReadRemoteFeatures,
hci_spec::StatusCode::kSuccess);
auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
/*conn=*/0x0000, hci_spec::LESupportedFeatures{0});
auto command2 = bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
auto command2_status_event = bt::testing::CommandStatusPacket(hci_spec::kReadRemoteVersionInfo,
hci_spec::StatusCode::kSuccess);
auto command2_cmpl_event = bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest), cb, /*wait=*/false,
hci_spec::kRemoteNameRequestCompleteEventCode);
EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event);
cmd_runner.QueueLeAsyncCommand(CommandPacket::New(hci_spec::kLEReadRemoteFeatures),
hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode, cb,
/*wait=*/false);
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kReadRemoteVersionInfo), cb, /*wait=*/true,
hci_spec::kReadRemoteVersionInfoCompleteEventCode);
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
// Command 2 should wait on command 0 & command 1 complete events.
EXPECT_FALSE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
// Completing the commands out of order shouldn't matter.
test_device()->SendCommandChannelPacket(command1_cmpl_event);
test_device()->SendCommandChannelPacket(command0_cmpl_event);
EXPECT_CMD_PACKET_OUT(test_device(), command2, &command2_status_event, &command2_cmpl_event);
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(3, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
}
TEST_F(SequentialCommandRunnerTest, ExclusiveAsyncCommands) {
auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
auto command0_status_event = bt::testing::CommandStatusPacket(hci_spec::kRemoteNameRequest,
hci_spec::StatusCode::kSuccess);
auto command0_cmpl_event = bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());
auto command1 = bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
auto command1_status_event = bt::testing::CommandStatusPacket(hci_spec::kReadRemoteVersionInfo,
hci_spec::StatusCode::kSuccess);
auto command1_cmpl_event = bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest), cb, /*wait=*/false,
hci_spec::kRemoteNameRequestCompleteEventCode);
// Even though command 1 is not waiting on command 0, it should remain queued due to the exclusion
// list.
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kReadRemoteVersionInfo), cb, /*wait=*/false,
hci_spec::kReadRemoteVersionInfoCompleteEventCode,
/*exclusions=*/{hci_spec::kRemoteNameRequest});
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
RunLoopUntilIdle();
EXPECT_FALSE(cmd_runner.IsReady());
// Command 1 is "sent" but queued in CommandChannel.
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
// Completing command 0 should send command 1.
test_device()->SendCommandChannelPacket(command0_cmpl_event);
EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event, &command1_cmpl_event);
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_EQ(2, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
}
TEST_F(SequentialCommandRunnerTest, CommandRunnerDestroyedBeforeSecondEventCallbackCalled) {
auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
auto command0_status_event = bt::testing::CommandStatusPacket(hci_spec::kRemoteNameRequest,
hci_spec::StatusCode::kSuccess);
auto command0_cmpl_event = bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());
auto command1 = bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
auto command1_status_event = bt::testing::CommandStatusPacket(hci_spec::kLEReadRemoteFeatures,
hci_spec::StatusCode::kSuccess);
auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
/*conn=*/0x0000, hci_spec::LESupportedFeatures{0});
std::optional<SequentialCommandRunner> cmd_runner;
cmd_runner.emplace(transport()->WeakPtr());
StartTestDevice();
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) {
if (cb_called == 0) {
cmd_runner.reset();
}
cb_called++;
};
EXPECT_FALSE(cmd_runner->HasQueuedCommands());
EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event, &command0_cmpl_event);
cmd_runner->QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest), cb, /*wait=*/false,
hci_spec::kRemoteNameRequestCompleteEventCode);
EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event);
cmd_runner->QueueLeAsyncCommand(CommandPacket::New(hci_spec::kLEReadRemoteFeatures),
hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode, cb,
/*wait=*/false);
EXPECT_TRUE(cmd_runner->IsReady());
EXPECT_TRUE(cmd_runner->HasQueuedCommands());
cmd_runner->RunCommands(status_cb);
EXPECT_FALSE(cmd_runner->IsReady());
RunLoopUntilIdle();
EXPECT_FALSE(cmd_runner.has_value());
EXPECT_EQ(1, cb_called);
EXPECT_EQ(0, status_cb_called);
}
TEST_F(SequentialCommandRunnerTest,
SequentialCommandRunnerDestroyedInCancelStatusCallbackDoesNotCrash) {
StartTestDevice();
std::optional<SequentialCommandRunner> cmd_runner;
cmd_runner.emplace(transport()->WeakPtr());
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
cmd_runner.reset();
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
auto command = bt::testing::EmptyCommandPacket(kTestOpCode);
cmd_runner->QueueCommand(CommandPacket::New(kTestOpCode), cb);
EXPECT_CMD_PACKET_OUT(test_device(), command);
cmd_runner->RunCommands(status_cb);
EXPECT_FALSE(cmd_runner->IsReady());
cmd_runner->Cancel();
RunLoopUntilIdle();
EXPECT_FALSE(cmd_runner);
EXPECT_EQ(0, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(ToResult(HostError::kCanceled), status);
}
TEST_F(SequentialCommandRunnerTest, QueueCommandsWhileAlreadyRunning) {
auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
auto command0_status_event = bt::testing::CommandStatusPacket(hci_spec::kRemoteNameRequest,
hci_spec::StatusCode::kSuccess);
auto command0_cmpl_event = bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());
auto command1 = bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
auto command1_status_event = bt::testing::CommandStatusPacket(hci_spec::kLEReadRemoteFeatures,
hci_spec::StatusCode::kSuccess);
auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
/*conn=*/0x0000, hci_spec::LESupportedFeatures{0});
auto command2 = bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
auto command2_status_event = bt::testing::CommandStatusPacket(hci_spec::kReadRemoteVersionInfo,
hci_spec::StatusCode::kSuccess);
auto command2_cmpl_event = bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);
StartTestDevice();
SequentialCommandRunner cmd_runner(transport()->WeakPtr());
Result<> status = fitx::ok();
int status_cb_called = 0;
auto status_cb = [&](Result<> cb_status) {
status = cb_status;
status_cb_called++;
};
int cb_called = 0;
auto cb = [&](const EventPacket& event) { cb_called++; };
int name_cb_called = 0;
auto name_request_callback = [&](const EventPacket& event) {
name_cb_called++;
EXPECT_FALSE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event, &command1_cmpl_event);
cmd_runner.QueueLeAsyncCommand(CommandPacket::New(hci_spec::kLEReadRemoteFeatures),
hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode, cb,
/*wait=*/false);
EXPECT_CMD_PACKET_OUT(test_device(), command2, &command2_status_event, &command2_cmpl_event);
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kReadRemoteVersionInfo), cb,
/*wait=*/false, hci_spec::kReadRemoteVersionInfoCompleteEventCode);
};
EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event, &command0_cmpl_event);
cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest), name_request_callback,
/*wait=*/false, hci_spec::kRemoteNameRequestCompleteEventCode);
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_TRUE(cmd_runner.HasQueuedCommands());
cmd_runner.RunCommands(status_cb);
EXPECT_FALSE(cmd_runner.IsReady());
RunLoopUntilIdle();
EXPECT_TRUE(cmd_runner.IsReady());
EXPECT_FALSE(cmd_runner.HasQueuedCommands());
EXPECT_EQ(1, name_cb_called);
EXPECT_EQ(2, cb_called);
EXPECT_EQ(1, status_cb_called);
EXPECT_EQ(fitx::ok(), status);
}
} // namespace
} // namespace bt::hci