switch-pico/tests/configuration_storage_test.cpp

658 lines
31 KiB
C++

#include "configuration/adapter_configuration.h"
#include "configuration/configuration_storage.h"
#include "configuration/configuration_transaction.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
namespace {
constexpr size_t kSectorSize = 4096;
constexpr size_t kPageSize = 256;
struct FakeFlash {
uint8_t bytes[CONFIGURATION_STORAGE_COPY_COUNT][kSectorSize];
int successful_programs = 0;
int fail_after_programs = -1;
FakeFlash() { memset(bytes, 0xff, sizeof(bytes)); }
};
void require(bool condition, const char* message) {
if (!condition) {
std::cerr << message << '\n';
std::exit(1);
}
}
bool fake_read(void* context, uint8_t copy, size_t offset,
uint8_t* output, size_t size) {
auto* flash = static_cast<FakeFlash*>(context);
if (copy >= CONFIGURATION_STORAGE_COPY_COUNT ||
offset + size > kSectorSize) {
return false;
}
memcpy(output, &flash->bytes[copy][offset], size);
return true;
}
bool fake_erase(void* context, uint8_t copy) {
auto* flash = static_cast<FakeFlash*>(context);
if (copy >= CONFIGURATION_STORAGE_COPY_COUNT) {
return false;
}
memset(flash->bytes[copy], 0xff, kSectorSize);
return true;
}
bool fake_program(void* context, uint8_t copy, size_t offset,
const uint8_t* data, size_t size) {
auto* flash = static_cast<FakeFlash*>(context);
if (copy >= CONFIGURATION_STORAGE_COPY_COUNT || size != kPageSize ||
offset + size > kSectorSize) {
return false;
}
if (flash->fail_after_programs >= 0 &&
flash->successful_programs >= flash->fail_after_programs) {
return false;
}
for (size_t index = 0; index < size; ++index) {
flash->bytes[copy][offset + index] &= data[index];
}
++flash->successful_programs;
return true;
}
ConfigurationStorageIo fake_io(FakeFlash* flash) {
return {
flash,
kSectorSize,
kPageSize,
fake_read,
fake_erase,
fake_program,
};
}
ControllerIdentity nintendo_identity(uint8_t address_suffix,
uint16_t product_id = 0x2009) {
ControllerIdentity identity{};
identity.stable = true;
identity.transport = ControllerTransport::kClassic;
identity.address[0] = 0x02;
identity.address[5] = address_suffix;
identity.vendor_id = 0x057e;
identity.product_id = product_id;
return identity;
}
void test_schema_encoding() {
AdapterConfiguration configuration{};
configuration.pairing_window_seconds = 90;
configuration.requested_mode = AdapterRequestedMode::kXInput;
configuration.joycon_mode = JoyConMode::kIndividual;
uint8_t payload[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_encode(configuration, payload,
sizeof(payload)),
"valid v4 configuration did not encode");
const uint8_t expected[ADAPTER_CONFIGURATION_ENCODED_SIZE] = {
90,
0,
static_cast<uint8_t>(AdapterRequestedMode::kXInput),
0,
static_cast<uint8_t>(JoyConMode::kIndividual),
0,
0,
0,
};
require(memcmp(payload, expected, sizeof(expected)) == 0,
"v4 configuration bytes are not canonical");
AdapterConfiguration decoded{};
require(adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, payload,
sizeof(payload), &decoded) &&
decoded.pairing_window_seconds == 90 &&
decoded.requested_mode == AdapterRequestedMode::kXInput &&
decoded.joycon_mode == JoyConMode::kIndividual &&
decoded.native_switch_controller_count == 0,
"v4 configuration did not round trip");
const AdapterRequestedMode valid_modes[] = {
AdapterRequestedMode::kAuto,
AdapterRequestedMode::kSwitch,
AdapterRequestedMode::kXInput,
AdapterRequestedMode::kDInput,
AdapterRequestedMode::kMac,
};
for (AdapterRequestedMode mode : valid_modes) {
configuration.requested_mode = mode;
require(adapter_configuration_encode(configuration, payload,
sizeof(payload)) &&
adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, payload,
sizeof(payload), &decoded) &&
decoded.requested_mode == mode &&
decoded.joycon_mode == JoyConMode::kIndividual,
"valid requested mode did not round trip");
}
AdapterModeAvailability availability{};
require(!adapter_requested_mode_available(
AdapterRequestedMode::kDInput, availability) &&
!adapter_requested_mode_available(
AdapterRequestedMode::kMac, availability),
"future modes were available by default");
availability.dinput_mode = true;
availability.mac_mode = true;
require(adapter_requested_mode_available(
AdapterRequestedMode::kDInput, availability) &&
adapter_requested_mode_available(
AdapterRequestedMode::kMac, availability),
"availability API could not enable future modes");
const uint8_t legacy[] = {120, 0, 0, 0};
decoded.native_switch_controller_count = 1;
decoded.native_switch_controllers[0] = nintendo_identity(1);
require(adapter_configuration_decode(
ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION, legacy,
sizeof(legacy), &decoded) &&
decoded.pairing_window_seconds == 120 &&
decoded.requested_mode == AdapterRequestedMode::kAuto &&
decoded.joycon_mode == JoyConMode::kPaired &&
decoded.native_switch_controller_count == 0,
"v1 configuration did not migrate without approvals");
uint8_t malformed_legacy[sizeof(legacy)];
memcpy(malformed_legacy, legacy, sizeof(legacy));
malformed_legacy[3] = 1;
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION,
malformed_legacy, sizeof(malformed_legacy), &decoded),
"v1 nonzero reserved byte was accepted");
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION, legacy,
sizeof(legacy) - 1, &decoded),
"v1 record with wrong size was accepted");
const uint8_t v2[] = {
120, 0, static_cast<uint8_t>(AdapterRequestedMode::kMac), 0, 0, 0, 0, 0,
};
require(adapter_configuration_decode(
ADAPTER_CONFIGURATION_V2_SCHEMA_VERSION, v2, sizeof(v2),
&decoded) &&
decoded.pairing_window_seconds == 120 &&
decoded.requested_mode == AdapterRequestedMode::kMac &&
decoded.joycon_mode == JoyConMode::kPaired &&
decoded.native_switch_controller_count == 0,
"v2 migration changed settings or granted native rumble");
for (size_t index = 3; index < sizeof(v2); ++index) {
uint8_t malformed[sizeof(v2)];
memcpy(malformed, v2, sizeof(v2));
malformed[index] = 1;
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_V2_SCHEMA_VERSION, malformed,
sizeof(malformed), &decoded),
"v2 nonzero reserved byte was accepted");
}
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_V2_SCHEMA_VERSION, v2, sizeof(v2) - 1,
&decoded),
"v2 record with wrong size was accepted");
for (size_t index = 3; index < sizeof(payload); ++index) {
if (index == 4) {
continue;
}
uint8_t malformed[sizeof(payload)];
memcpy(malformed, payload, sizeof(payload));
malformed[index] = 1;
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, malformed,
sizeof(malformed), &decoded),
"v4 nonzero reserved or unused byte was accepted");
}
uint8_t invalid_mode[sizeof(payload)];
memcpy(invalid_mode, payload, sizeof(payload));
invalid_mode[2] = 5;
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, invalid_mode,
sizeof(invalid_mode), &decoded),
"out-of-range requested mode was accepted");
const uint8_t invalid_joycon_modes[] = {2, 0xff};
for (uint8_t mode : invalid_joycon_modes) {
memcpy(invalid_mode, payload, sizeof(payload));
invalid_mode[4] = mode;
require(!adapter_configuration_decode(invalid_mode, sizeof(invalid_mode),
&decoded),
"out-of-range Joy-Con2 mode was accepted");
configuration.joycon_mode = static_cast<JoyConMode>(mode);
require(!adapter_configuration_encode(configuration, payload,
sizeof(payload)),
"out-of-range Joy-Con2 mode encoded");
}
configuration.joycon_mode = JoyConMode::kPaired;
require(adapter_configuration_encode(configuration, payload, sizeof(payload)) &&
adapter_configuration_decode(payload, sizeof(payload), &decoded) &&
decoded.joycon_mode == JoyConMode::kPaired,
"Paired mode did not round trip");
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, payload,
sizeof(payload) - 1, &decoded),
"short v4 record was accepted");
uint8_t oversized[ADAPTER_CONFIGURATION_ENCODED_SIZE + 1]{};
memcpy(oversized, payload, sizeof(payload));
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_SCHEMA_VERSION, oversized,
sizeof(oversized), &decoded),
"oversized v4 record was accepted");
configuration.pairing_window_seconds = 9;
require(!adapter_configuration_encode(configuration, payload,
sizeof(payload)),
"out-of-range pairing window was accepted");
configuration.pairing_window_seconds = 90;
configuration.requested_mode =
static_cast<AdapterRequestedMode>(5);
require(!adapter_configuration_encode(configuration, payload,
sizeof(payload)),
"out-of-range requested mode encoded");
configuration.requested_mode = AdapterRequestedMode::kAuto;
require(!adapter_configuration_encode(configuration, oversized,
sizeof(oversized)),
"v4 encoder accepted a noncanonical output size");
}
void test_native_switch_approval_identity_and_canonical_encoding() {
const ControllerIdentity pro = nintendo_identity(3);
const ControllerIdentity left = nintendo_identity(1, 0x2006);
const ControllerIdentity right = nintendo_identity(2, 0x2007);
AdapterConfiguration configuration{};
require(!adapter_configuration_native_switch_approved(configuration, pro),
"a supported model was approved without an explicit identity");
configuration.native_switch_controller_count = 3;
configuration.native_switch_controllers[0] = pro;
configuration.native_switch_controllers[1] = left;
configuration.native_switch_controllers[2] = right;
uint8_t payload[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_encode(configuration, payload, sizeof(payload)),
"original Switch controller approvals did not encode");
AdapterConfiguration decoded{};
require(adapter_configuration_decode(payload, sizeof(payload), &decoded) &&
adapter_configuration_native_switch_approved(decoded, pro) &&
adapter_configuration_native_switch_approved(decoded, left) &&
adapter_configuration_native_switch_approved(decoded, right) &&
!adapter_configuration_native_switch_approved(
decoded, nintendo_identity(4)) &&
!adapter_configuration_native_switch_approved(
decoded, nintendo_identity(3, 0x2006)),
"approval did not remain specific to the complete controller identity");
decoded.joycon_mode = JoyConMode::kIndividual;
require(adapter_configuration_decode(
ADAPTER_CONFIGURATION_V3_SCHEMA_VERSION, payload,
sizeof(payload), &decoded) &&
decoded.joycon_mode == JoyConMode::kPaired &&
adapter_configuration_native_switch_approved(decoded, pro) &&
adapter_configuration_native_switch_approved(decoded, left) &&
adapter_configuration_native_switch_approved(decoded, right),
"v3 migration erased approvals or retained a stale player mode");
for (size_t index = 4; index < ADAPTER_CONFIGURATION_HEADER_SIZE; ++index) {
uint8_t malformed[sizeof(payload)];
memcpy(malformed, payload, sizeof(payload));
malformed[index] = 1;
require(!adapter_configuration_decode(
ADAPTER_CONFIGURATION_V3_SCHEMA_VERSION, malformed,
sizeof(malformed), &decoded),
"v3 reserved header byte was accepted");
}
configuration.native_switch_controllers[0] = right;
configuration.native_switch_controllers[1] = pro;
configuration.native_switch_controllers[2] = left;
uint8_t reordered[sizeof(payload)]{};
require(adapter_configuration_encode(configuration, reordered,
sizeof(reordered)) &&
memcmp(payload, reordered, sizeof(payload)) == 0,
"approval insertion order changed the persisted bytes or CRC");
configuration.native_switch_controller_count = 0;
require(!adapter_configuration_native_switch_approved(configuration, pro) &&
adapter_configuration_encode(configuration, reordered,
sizeof(reordered)) &&
adapter_configuration_decode(reordered, sizeof(reordered),
&decoded) &&
!adapter_configuration_native_switch_approved(decoded, pro),
"revoked array entries remained approved after encode and decode");
configuration.native_switch_controller_count =
ADAPTER_CONFIGURATION_NATIVE_SWITCH_CONTROLLER_CAPACITY;
for (size_t index = 0;
index < configuration.native_switch_controller_count; ++index) {
configuration.native_switch_controllers[index] =
nintendo_identity(static_cast<uint8_t>(
configuration.native_switch_controller_count - index));
}
require(adapter_configuration_encode(configuration, payload, sizeof(payload)) &&
adapter_configuration_decode(payload, sizeof(payload), &decoded) &&
adapter_configuration_native_switch_approved(
decoded, nintendo_identity(1)) &&
adapter_configuration_native_switch_approved(
decoded, nintendo_identity(16)),
"a full approval array lost its boundary identities");
++configuration.native_switch_controller_count;
require(!adapter_configuration_encode(configuration, payload, sizeof(payload)) &&
!adapter_configuration_native_switch_approved(configuration, pro),
"an oversized approval array was accepted");
payload[3] = configuration.native_switch_controller_count;
require(!adapter_configuration_decode(payload, sizeof(payload), &decoded),
"an oversized encoded approval count was accepted");
}
void test_native_switch_approval_rejects_invalid_records() {
AdapterConfiguration configuration{};
configuration.native_switch_controller_count = 2;
configuration.native_switch_controllers[0] = nintendo_identity(1);
configuration.native_switch_controllers[1] = nintendo_identity(2);
uint8_t payload[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_encode(configuration, payload, sizeof(payload)),
"approval validation fixture did not encode");
configuration.native_switch_controllers[1] =
configuration.native_switch_controllers[0];
uint8_t malformed[sizeof(payload)]{};
require(!adapter_configuration_encode(configuration, malformed,
sizeof(malformed)),
"duplicate approval identities encoded");
memcpy(malformed, payload, sizeof(payload));
constexpr size_t first = ADAPTER_CONFIGURATION_HEADER_SIZE;
constexpr size_t second = first + CONTROLLER_IDENTITY_ENCODED_SIZE;
memcpy(malformed + second, malformed + first,
CONTROLLER_IDENTITY_ENCODED_SIZE);
AdapterConfiguration decoded{};
require(!adapter_configuration_decode(malformed, sizeof(malformed), &decoded),
"duplicate encoded approval identities were accepted");
memcpy(malformed + first, payload + second, CONTROLLER_IDENTITY_ENCODED_SIZE);
memcpy(malformed + second, payload + first, CONTROLLER_IDENTITY_ENCODED_SIZE);
require(!adapter_configuration_decode(malformed, sizeof(malformed), &decoded),
"noncanonical approval ordering was accepted");
configuration.native_switch_controller_count = 1;
ControllerIdentity invalid[] = {
controller_identity_global(), nintendo_identity(1),
nintendo_identity(1), nintendo_identity(1), nintendo_identity(1, 0x2069),
};
invalid[1].stable = false;
invalid[2].transport = ControllerTransport::kBle;
invalid[3].vendor_id = 0x1234;
for (const ControllerIdentity& identity : invalid) {
configuration.native_switch_controllers[0] = identity;
require(!adapter_configuration_encode(configuration, malformed,
sizeof(malformed)) &&
!adapter_configuration_native_switch_approved(
configuration, identity),
"global, unstable, BLE, or ineligible controller was approved");
}
const uint8_t invalid_fields[][2] = {
{0, 0}, {0, 2}, {1, 0}, {1, 2}, {1, 3}, {3, 1},
{10, 0x34}, {12, 0x69},
};
for (const auto& field : invalid_fields) {
memcpy(malformed, payload, sizeof(payload));
malformed[first + field[0]] = field[1];
require(!adapter_configuration_decode(malformed, sizeof(malformed),
&decoded),
"malformed or ineligible encoded identity was accepted");
}
memcpy(malformed, payload, sizeof(payload));
malformed[sizeof(malformed) - 1] = 1;
require(!adapter_configuration_decode(malformed, sizeof(malformed), &decoded),
"nonzero unused approval bytes were accepted");
}
void test_legacy_migration_power_loss() {
const uint16_t versions[] = {
ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION,
ADAPTER_CONFIGURATION_V2_SCHEMA_VERSION,
ADAPTER_CONFIGURATION_V3_SCHEMA_VERSION,
};
for (uint16_t version : versions) {
const bool v1 = version == ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION;
const bool v3 = version == ADAPTER_CONFIGURATION_V3_SCHEMA_VERSION;
AdapterConfiguration source{};
source.pairing_window_seconds = 90;
source.requested_mode = v1 ? AdapterRequestedMode::kAuto
: AdapterRequestedMode::kXInput;
if (v3) {
source.native_switch_controller_count = 2;
source.native_switch_controllers[0] = nintendo_identity(1);
source.native_switch_controllers[1] = nintendo_identity(2, 0x2006);
}
uint8_t legacy[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_encode(source, legacy, sizeof(legacy)),
"legacy migration fixture did not encode");
const size_t legacy_size =
v3 ? ADAPTER_CONFIGURATION_ENCODED_SIZE
: (v1 ? ADAPTER_CONFIGURATION_LEGACY_ENCODED_SIZE
: ADAPTER_CONFIGURATION_V2_ENCODED_SIZE);
FakeFlash flash;
ConfigurationStorage store;
require(store.initialize(fake_io(&flash)) &&
store.commit(version, legacy, legacy_size) ==
ConfigurationStorageResult::kOk,
"legacy migration seed did not persist");
AdapterConfiguration migrated{};
uint8_t payload[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_decode(version, legacy, legacy_size,
&migrated) &&
adapter_configuration_encode(migrated, payload,
sizeof(payload)),
"legacy settings did not convert to schema4");
flash.fail_after_programs = flash.successful_programs;
require(store.commit(ADAPTER_CONFIGURATION_SCHEMA_VERSION, payload,
sizeof(payload)) ==
ConfigurationStorageResult::kIoError,
"failed migration reported success");
flash.fail_after_programs = -1;
ConfigurationStorage recovered;
require(recovered.initialize(fake_io(&flash)) &&
recovered.snapshot().schema_version == version &&
recovered.snapshot().payload_size == legacy_size &&
memcmp(recovered.snapshot().payload, legacy, legacy_size) == 0,
"migration power loss destroyed the legacy settings");
require(recovered.commit(ADAPTER_CONFIGURATION_SCHEMA_VERSION, payload,
sizeof(payload)) ==
ConfigurationStorageResult::kOk,
"migration retry failed");
ConfigurationStorage rebooted;
AdapterConfiguration decoded{};
require(rebooted.initialize(fake_io(&flash)) &&
rebooted.snapshot().schema_version ==
ADAPTER_CONFIGURATION_SCHEMA_VERSION &&
adapter_configuration_decode(
rebooted.snapshot().schema_version,
rebooted.snapshot().payload,
rebooted.snapshot().payload_size, &decoded) &&
decoded.pairing_window_seconds == 90 &&
decoded.requested_mode == migrated.requested_mode &&
decoded.joycon_mode == JoyConMode::kPaired &&
decoded.native_switch_controller_count ==
source.native_switch_controller_count &&
(!v3 ||
(adapter_configuration_native_switch_approved(
decoded, source.native_switch_controllers[0]) &&
adapter_configuration_native_switch_approved(
decoded, source.native_switch_controllers[1]))),
"migration retry changed settings or controller approvals");
}
}
void test_two_copy_recovery() {
FakeFlash flash;
ConfigurationStorage store;
require(store.initialize(fake_io(&flash)),
"storage did not initialize");
require(!store.snapshot().valid,
"erased storage appeared valid");
const uint8_t first[] = {1, 2, 3, 4};
require(store.commit(1, first, sizeof(first)) ==
ConfigurationStorageResult::kOk &&
store.snapshot().generation == 1,
"first generation did not commit");
const int programs_after_first = flash.successful_programs;
require(store.commit(1, first, sizeof(first)) ==
ConfigurationStorageResult::kUnchanged &&
flash.successful_programs == programs_after_first,
"identical configuration consumed a flash write");
const uint8_t second[] = {5, 6, 7, 8};
require(store.commit(1, second, sizeof(second)) ==
ConfigurationStorageResult::kOk &&
store.snapshot().generation == 2,
"second generation did not commit");
ConfigurationStorage after_power_cycle;
require(after_power_cycle.initialize(fake_io(&flash)) &&
after_power_cycle.snapshot().generation == 2 &&
memcmp(after_power_cycle.snapshot().payload, second,
sizeof(second)) == 0,
"latest generation did not survive reinitialization");
flash.bytes[1][CONFIGURATION_STORAGE_RECORD_HEADER_SIZE] ^= 0x01;
ConfigurationStorage after_corruption;
require(after_corruption.initialize(fake_io(&flash)) &&
after_corruption.snapshot().generation == 1 &&
memcmp(after_corruption.snapshot().payload, first,
sizeof(first)) == 0,
"corrupt newest generation did not roll back");
}
void test_interrupted_write_retains_previous_generation() {
FakeFlash flash;
ConfigurationStorage store;
require(store.initialize(fake_io(&flash)),
"storage did not initialize for interruption test");
const uint8_t first[] = {9, 8, 7, 6};
require(store.commit(1, first, sizeof(first)) ==
ConfigurationStorageResult::kOk,
"baseline generation did not commit");
uint8_t large[300];
memset(large, 0x5a, sizeof(large));
flash.fail_after_programs = flash.successful_programs + 1;
require(store.commit(1, large, sizeof(large)) ==
ConfigurationStorageResult::kIoError,
"interrupted multi-page write reported success");
flash.fail_after_programs = -1;
ConfigurationStorage recovered;
require(recovered.initialize(fake_io(&flash)) &&
recovered.snapshot().generation == 1 &&
recovered.snapshot().payload_size == sizeof(first) &&
memcmp(recovered.snapshot().payload, first,
sizeof(first)) == 0,
"interrupted write replaced the previous generation");
}
void test_transaction_validation() {
AdapterConfiguration configuration{};
configuration.pairing_window_seconds = 120;
uint8_t payload[ADAPTER_CONFIGURATION_ENCODED_SIZE]{};
require(adapter_configuration_encode(configuration, payload,
sizeof(payload)),
"transaction payload did not encode");
const uint32_t crc = configuration_crc32(payload, sizeof(payload));
ConfigurationTransaction transaction;
require(transaction.begin(7, ADAPTER_CONFIGURATION_SCHEMA_VERSION,
sizeof(payload), crc) ==
ConfigurationTransactionStatus::kReceiving,
"valid transaction did not begin");
require(transaction.append(7, 1, payload, 1) ==
ConfigurationTransactionStatus::kOutOfOrder,
"out-of-order chunk was accepted");
require(transaction.begin(8, ADAPTER_CONFIGURATION_SCHEMA_VERSION,
sizeof(payload), crc ^ 1u) ==
ConfigurationTransactionStatus::kReceiving,
"replacement transaction did not begin");
require(transaction.append(8, 0, payload, 2) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.append(8, 2, &payload[2],
sizeof(payload) - 2) ==
ConfigurationTransactionStatus::kReceiving,
"ordered chunks were rejected");
require(transaction.finish(8) ==
ConfigurationTransactionStatus::kBadCrc,
"bad transaction CRC was accepted");
require(transaction.begin(9, ADAPTER_CONFIGURATION_SCHEMA_VERSION,
sizeof(payload), crc) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.append(9, 0, payload, sizeof(payload)) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.finish(9) ==
ConfigurationTransactionStatus::kPending,
"valid transaction did not reach pending commit");
require(transaction.begin(10, ADAPTER_CONFIGURATION_SCHEMA_VERSION,
sizeof(payload), crc) ==
ConfigurationTransactionStatus::kBusy,
"pending transaction was replaced");
transaction.set_result(ConfigurationTransactionStatus::kCommitted,
4, crc);
require(transaction.begin(11, 99, sizeof(payload), crc) ==
ConfigurationTransactionStatus::kUnsupportedSchema,
"unsupported schema was accepted");
require(transaction.begin(
12, ADAPTER_CONFIGURATION_SCHEMA_VERSION,
CONFIGURATION_STORAGE_MAX_PAYLOAD_SIZE + 1, crc) ==
ConfigurationTransactionStatus::kTooLarge,
"oversized transaction was accepted");
require(transaction.begin(
13, ADAPTER_CONFIGURATION_LEGACY_SCHEMA_VERSION,
ADAPTER_CONFIGURATION_LEGACY_ENCODED_SIZE, 0) ==
ConfigurationTransactionStatus::kUnsupportedSchema &&
transaction.begin(
14, ADAPTER_CONFIGURATION_V2_SCHEMA_VERSION,
ADAPTER_CONFIGURATION_V2_ENCODED_SIZE, 0) ==
ConfigurationTransactionStatus::kUnsupportedSchema &&
transaction.begin(
15, ADAPTER_CONFIGURATION_V3_SCHEMA_VERSION,
ADAPTER_CONFIGURATION_ENCODED_SIZE, 0) ==
ConfigurationTransactionStatus::kUnsupportedSchema,
"legacy host writes could silently erase stored settings");
payload[3] = ADAPTER_CONFIGURATION_NATIVE_SWITCH_CONTROLLER_CAPACITY + 1;
require(transaction.begin(
16, ADAPTER_CONFIGURATION_SCHEMA_VERSION, sizeof(payload),
configuration_crc32(payload, sizeof(payload))) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.append(16, 0, payload, sizeof(payload)) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.finish(16) ==
ConfigurationTransactionStatus::kMalformed,
"valid CRC allowed a malformed approval list to reach storage");
payload[3] = 0;
payload[4] = 2;
require(transaction.begin(
17, ADAPTER_CONFIGURATION_SCHEMA_VERSION, sizeof(payload),
configuration_crc32(payload, sizeof(payload))) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.append(17, 0, payload, sizeof(payload)) ==
ConfigurationTransactionStatus::kReceiving &&
transaction.finish(17) ==
ConfigurationTransactionStatus::kMalformed,
"valid CRC allowed an invalid Joy-Con2 mode to reach storage");
}
} // namespace
int main() {
test_schema_encoding();
test_native_switch_approval_identity_and_canonical_encoding();
test_native_switch_approval_rejects_invalid_records();
test_legacy_migration_power_loss();
test_two_copy_recovery();
test_interrupted_write_retains_previous_generation();
test_transaction_validation();
return 0;
}