switch-pico/tests/switch2_wake_test.cpp

284 lines
9.8 KiB
C++

#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <btstack.h>
#include "bluetooth_transport_config.h"
namespace {
struct SubmittedCommand {
uint16_t opcode;
uint8_t address[6];
uint16_t interval_min;
uint16_t interval_max;
uint8_t advertising_type;
uint8_t own_address_type;
uint8_t channel_map;
uint8_t filter_policy;
uint8_t data[31];
uint8_t data_length;
uint8_t enabled;
};
uint32_t now_ms;
bd_addr_t original_address = {0xA8, 0x59, 0x5F, 0x9E, 0x37, 0x65};
btstack_packet_handler_t event_handler;
btstack_timer_source_t* installed_timer;
SubmittedCommand submitted[16]{};
size_t submitted_count;
bool command_available = true;
void require(bool condition, const char* message) {
if (!condition) {
std::cerr << message << '\n';
std::exit(1);
}
}
} // namespace
const hci_cmd_t hci_read_bd_addr = {0x1009, "read-address"};
const hci_cmd_t hci_le_set_advertising_parameters = {0x2006, "params"};
const hci_cmd_t hci_le_set_advertising_data = {0x2008, "data"};
const hci_cmd_t hci_le_set_advertise_enable = {0x200a, "enable"};
bool hci_can_send_command_packet_now() {
return command_available;
}
uint8_t hci_send_cmd(const hci_cmd_t* command, ...) {
require(command_available, "submitted without an HCI command credit");
require(submitted_count < 16, "command capture overflow");
command_available = false;
SubmittedCommand& output = submitted[submitted_count++];
output.opcode = command->opcode;
va_list arguments;
va_start(arguments, command);
if (command->opcode == 0xfc01) {
const uint8_t* address = va_arg(arguments, const uint8_t*);
memcpy(output.address, address, sizeof(output.address));
} else if (command->opcode == 0x2006) {
output.interval_min = static_cast<uint16_t>(va_arg(arguments, int));
output.interval_max = static_cast<uint16_t>(va_arg(arguments, int));
output.advertising_type = static_cast<uint8_t>(va_arg(arguments, int));
output.own_address_type = static_cast<uint8_t>(va_arg(arguments, int));
(void)va_arg(arguments, int);
(void)va_arg(arguments, const uint8_t*);
output.channel_map = static_cast<uint8_t>(va_arg(arguments, int));
output.filter_policy = static_cast<uint8_t>(va_arg(arguments, int));
} else if (command->opcode == 0x2008) {
output.data_length = static_cast<uint8_t>(va_arg(arguments, int));
const uint8_t* data = va_arg(arguments, const uint8_t*);
memcpy(output.data, data, output.data_length);
} else if (command->opcode == 0x200a) {
output.enabled = static_cast<uint8_t>(va_arg(arguments, int));
}
va_end(arguments);
return ERROR_CODE_SUCCESS;
}
void hci_add_event_handler(
btstack_packet_callback_registration_t* registration) {
event_handler = registration->callback;
}
void gap_local_bd_addr(bd_addr_t output) {
memcpy(output, original_address, sizeof(original_address));
}
uint32_t btstack_run_loop_get_time_ms() {
return now_ms;
}
void btstack_run_loop_set_timer_handler(
btstack_timer_source_t* timer,
void (*handler)(btstack_timer_source_t*)) {
timer->handler = handler;
installed_timer = timer;
}
void btstack_run_loop_set_timer(
btstack_timer_source_t* timer, uint32_t timeout_ms) {
timer->timeout_ms = timeout_ms;
}
void btstack_run_loop_add_timer(btstack_timer_source_t* timer) {
++timer->add_count;
}
int btstack_run_loop_remove_timer(btstack_timer_source_t*) {
return 1;
}
#define SWITCH2_WAKE_CONFIGURED 1
#define SWITCH2_WAKE_SOURCE_ADDRESS_BYTES \
{0x98, 0xE2, 0x55, 0x07, 0xDF, 0x00}
#define SWITCH2_WAKE_ADVERTISEMENT_DATA_BYTES \
{0x02, 0x01, 0x06, 0x1B, 0xFF, 0x53, 0x05, 0x01, 0x00, 0x03, 0x7E, \
0x05, 0x66, 0x20, 0x00, 0x01, 0x81, 0xBD, 0xD6, 0xF7, 0xEB, 0xF1, \
0x48, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#include "input/switch2_wake.cpp"
namespace {
void complete(uint16_t opcode, uint8_t status = 0,
const uint8_t* address = nullptr) {
uint8_t event[12] = {
HCI_EVENT_COMMAND_COMPLETE,
static_cast<uint8_t>(address == nullptr ? 4 : 10),
1,
static_cast<uint8_t>(opcode), static_cast<uint8_t>(opcode >> 8),
status,
};
if (address != nullptr) {
for (size_t index = 0; index < 6; ++index) {
event[6 + index] = address[5 - index];
}
}
command_available = true;
event_handler(
HCI_EVENT_PACKET, 0, event,
static_cast<uint16_t>(address == nullptr ? 6 : 12));
}
void run_task() {
require(installed_timer != nullptr && installed_timer->handler != nullptr,
"wake timer was not installed");
installed_timer->handler(installed_timer);
}
void require_opcode(size_t index, uint16_t opcode) {
require(index < submitted_count && submitted[index].opcode == opcode,
"unexpected HCI command sequence");
}
void test_stable_identity_wake() {
switch2_wake_initialize();
Switch2WakeDiagnostics diagnostics{};
switch2_wake_diagnostics(&diagnostics);
require(diagnostics.configured && diagnostics.busy &&
!switch2_wake_ready_for_connections() &&
!switch2_wake_request(),
"controller connections or wake escaped startup identity setup");
run_task();
require_opcode(0, 0xfc01);
const uint8_t wake_address[] = {0x98, 0xE2, 0x55, 0x07, 0xDF, 0x00};
require(memcmp(submitted[0].address, wake_address, 6) == 0,
"stable wake address did not reach the radio command");
complete(0xfc01);
run_task();
require_opcode(1, 0x1009);
complete(0x1009, 0, wake_address);
require(switch2_wake_ready_for_connections() && !g_timer_armed,
"verified stable identity did not admit connections and go idle");
switch2_wake_diagnostics(&diagnostics);
require(!diagnostics.busy && switch2_wake_request() &&
!switch2_wake_request(),
"wake requests were not bounded while busy");
run_task();
require_opcode(2, 0x2006);
require(submitted[2].interval_min == 0x20 &&
submitted[2].interval_max == 0x20 &&
submitted[2].advertising_type == 3 &&
submitted[2].own_address_type == 0 &&
submitted[2].channel_map == 7 &&
submitted[2].filter_policy == 0,
"known-working advertising parameters changed");
complete(0x2006);
run_task();
require_opcode(3, 0x2008);
require(submitted[3].data_length == 31 &&
submitted[3].data[16] == 0x81,
"captured wake payload was not submitted intact");
complete(0x2008);
run_task();
require_opcode(4, 0x200a);
require(submitted[4].enabled == 1,
"wake advertising was not enabled");
complete(0x200a);
require(g_timer_armed && installed_timer->timeout_ms == 2000,
"wake burst did not schedule one exact stop deadline");
now_ms = 1999;
run_task();
require(submitted_count == 5,
"wake burst stopped before two seconds");
now_ms = 2000;
run_task();
require_opcode(5, 0x200a);
require(submitted[5].enabled == 0,
"wake advertising was not disabled");
complete(0x200a);
switch2_wake_diagnostics(&diagnostics);
require(!diagnostics.busy && diagnostics.accepted_requests == 1 &&
diagnostics.completed_bursts == 1 &&
diagnostics.failures == 0 && !g_timer_armed,
"completed wake did not return to a dormant idle state");
require(submitted_count == 6,
"wake burst changed the public identity after startup");
}
void test_failed_wake_keeps_stable_identity() {
require(switch2_wake_request(),
"idle module rejected a second wake request");
run_task();
require_opcode(6, 0x2006);
complete(0x2006, 0x12);
run_task();
require(submitted_count == 7,
"wake setup failure issued an address reset");
Switch2WakeDiagnostics diagnostics{};
switch2_wake_diagnostics(&diagnostics);
require(!diagnostics.busy && diagnostics.failures == 1 &&
switch2_wake_ready_for_connections(),
"wake failure disrupted the stable controller identity");
}
void test_disabled_transport_wake() {
switch2_wake_initialize();
switch2_wake_initialize();
Switch2WakeDiagnostics diagnostics{};
switch2_wake_diagnostics(&diagnostics);
require(!diagnostics.configured && !switch2_wake_ready_for_connections() &&
!switch2_wake_request(),
"Classic-only wake must be unavailable while preserving the paired host identity");
run_task();
require_opcode(0, 0xfc01);
const uint8_t paired_address[] = SWITCH2_WAKE_SOURCE_ADDRESS_BYTES;
require(memcmp(submitted[0].address, paired_address, sizeof(paired_address)) == 0,
"Classic-only startup changed the address used by existing bonds");
complete(0xfc01);
run_task();
require_opcode(1, 0x1009);
complete(0x1009, 0, paired_address);
require(switch2_wake_ready_for_connections() && !switch2_wake_request(),
"Classic controller startup did not resume after identity verification");
now_ms = 10000;
run_task();
switch2_wake_diagnostics(&diagnostics);
require(!diagnostics.configured && !diagnostics.busy &&
submitted_count == 2 && command_available &&
diagnostics.accepted_requests == 0 &&
diagnostics.completed_bursts == 0 && diagnostics.failures == 0 &&
!switch2_wake_request(),
"Classic-only wake submitted BLE advertising or remained busy");
}
} // namespace
int main() {
if (SWITCH_PICO_ENABLE_BLE) {
test_stable_identity_wake();
test_failed_wake_keeps_stable_identity();
} else {
test_disabled_transport_wake();
}
return 0;
}