#include #include #include #include "parser/uni_hid_parser_ds4.h" #include "parser/uni_hid_parser_psmove.h" #include "parser/uni_hid_parser_switch.h" #include "parser/uni_hid_parser_native_motion.h" #include "uni_hid_device.h" #include "uni_utils.h" // Real staged parsers and normalization. Only radio, clock and platform edges // are substituted; fixtures enter public setup/feature/input APIs. static uint8_t command[128]; static unsigned command_len, ready_count; void uni_log(const char* fmt, ...) { (void)fmt; } void printf_hexdump(const void* data, int len) { (void)data; (void)len; } void uni_hid_device_send_ctrl_report(uni_hid_device_t* d, const uint8_t* bytes, uint16_t len) { (void)d; assert(len <= sizeof(command)); memcpy(command, bytes, len); command_len = len; } void uni_hid_device_send_intr_report(uni_hid_device_t* d, const uint8_t* bytes, uint16_t len) { uni_hid_device_send_ctrl_report(d, bytes, len); } int l2cap_can_send_packet_now(uint16_t cid) { (void)cid; return 1; } int l2cap_send(uint16_t cid, uint8_t* bytes, uint16_t len) { (void)cid; uni_hid_device_send_intr_report(NULL, bytes, len); return ERROR_CODE_SUCCESS; } uint8_t l2cap_request_can_send_now_event(uint16_t cid) { (void)cid; return 0; } uint32_t btstack_run_loop_get_time_ms(void) { return 0; } void btstack_run_loop_set_timer(btstack_timer_source_t* t, uint32_t ms) { (void)t; (void)ms; } void btstack_run_loop_add_timer(btstack_timer_source_t* t) { (void)t; } bool btstack_run_loop_remove_timer(btstack_timer_source_t* t) { (void)t; return false; } void btstack_run_loop_set_timer_context(btstack_timer_source_t* t, void* p) { t->context = p; } void btstack_run_loop_set_timer_handler(btstack_timer_source_t* t, void (*fn)(btstack_timer_source_t*)) { t->process = fn; } void* btstack_run_loop_get_timer_context(btstack_timer_source_t* t) { return t->context; } bool uni_hid_device_set_ready_complete(uni_hid_device_t* d) { (void)d; ++ready_count; return true; } uni_hid_device_t* uni_hid_device_create_virtual(uni_hid_device_t* d) { (void)d; return NULL; } void uni_hid_device_set_cod(uni_hid_device_t* d, uint32_t cod) { (void)d; (void)cod; } void uni_hid_device_connect(uni_hid_device_t* d) { (void)d; } void uni_hid_device_process_controller(uni_hid_device_t* d) { (void)d; } void uni_hid_device_set_product_id(uni_hid_device_t* d, uint16_t pid) { d->product_id = pid; } void uni_hid_device_set_vendor_id(uni_hid_device_t* d, uint16_t vid) { d->vendor_id = vid; } uint8_t uni_hid_parser_hat_to_dpad(uint8_t hat) { return hat == 0 ? DPAD_UP : 0; } void uni_hid_parser_wii_setup(uni_hid_device_t* d) { (void)d; assert(false); } bool uni_hid_parser_wii_accel_snapshot(uni_hid_device_t* d, int32_t v[3], uint32_t* s) { (void)d; (void)v; (void)s; assert(false); return false; } bool uni_hid_parser_wii_gyro_snapshot(uni_hid_device_t* d, int32_t v[3], uint32_t* s) { (void)d; (void)v; (void)s; assert(false); return false; } static void put16(uint8_t* bytes, int value) { bytes[0] = (uint16_t)value; bytes[1] = (uint16_t)value >> 8; } static uni_native_motion_snapshot_t snapshot(uni_hid_device_t* d) { uni_native_motion_snapshot_t result; assert(uni_hid_parser_native_motion_snapshot(d, &result)); return result; } static void ds4_seal(uint8_t bytes[78]) { const uint8_t transaction = 0xa1; uint32_t crc = ~uni_crc32_le(uni_crc32_le(UINT32_MAX, &transaction, 1), bytes, 74); for (unsigned i = 0; i < 4; ++i) bytes[74 + i] = crc >> (8 * i); } static void ds4_input(uni_hid_device_t* d, uint8_t bytes[78], uint16_t tick) { put16(bytes + 12, tick); ds4_seal(bytes); uni_hid_parser_ds4_init_report(d); uni_hid_parser_ds4_parse_input_report(d, bytes, 78); } static void ds4_provenance(void) { uni_hid_device_t d = {0}; d.report_parser.setup = uni_hid_parser_ds4_setup; uni_hid_parser_ds4_setup(&d); uint8_t bytes[78] = {0x11}; bytes[7] = 0x28; // Cross, neutral hat. put16(bytes + 19, 8192); // Gyro Z. put16(bytes + 23, 8192); // Acceleration Y. ds4_input(&d, bytes, 65530); uni_native_motion_snapshot_t first = snapshot(&d); assert(first.report_valid && !first.accel_valid && !first.gyro_valid); assert(d.controller.gamepad.buttons & BUTTON_A); uint8_t calibration[37] = {2}; for (unsigned i = 0; i < 3; ++i) { put16(calibration + 7 + 2 * i, 100); put16(calibration + 13 + 2 * i, -100); put16(calibration + 23 + 4 * i, 8192); put16(calibration + 25 + 4 * i, -8192); } put16(calibration + 19, 100); put16(calibration + 21, 100); uni_hid_parser_ds4_parse_feature_report(&d, calibration, 36); ds4_input(&d, bytes, 65531); assert(!snapshot(&d).accel_valid); uni_hid_parser_ds4_parse_feature_report(&d, calibration, 37); ds4_input(&d, bytes, 65531); assert(!snapshot(&d).accel_valid); // Calibration cannot rejuvenate a cached tick. ds4_input(&d, bytes, 2); // Wrapped sensor clock is forward progress. first = snapshot(&d); assert(first.accel_valid && first.gyro_valid && first.accel_q13[1] == 8192); ds4_input(&d, bytes, 2); assert(snapshot(&d).accel_sequence == first.accel_sequence); ds4_input(&d, bytes, 1); assert(snapshot(&d).gyro_sequence == first.gyro_sequence); bytes[7] ^= 0x20; // Corrupt input must not be admitted as button release. uni_hid_parser_ds4_parse_input_report(&d, bytes, 78); assert(!snapshot(&d).report_valid); assert(snapshot(&d).accel_sequence == first.accel_sequence); uint8_t buttons[10] = {1}; uni_hid_parser_ds4_init_report(&d); uni_hid_parser_ds4_parse_input_report(&d, buttons, sizeof(buttons)); assert(snapshot(&d).report_valid && snapshot(&d).accel_sequence == first.accel_sequence); // Valid accel calibration with degenerate gyro extrema is accel-only. memset(calibration + 7, 0, 12); uni_hid_parser_ds4_parse_feature_report(&d, calibration, 37); ds4_input(&d, bytes, 3); assert(snapshot(&d).accel_valid && !snapshot(&d).gyro_valid); uni_hid_parser_native_motion_forget(&d); uni_hid_parser_ds4_setup(&d); // Same pointer, new parser lifetime. assert(!snapshot(&d).report_valid && !snapshot(&d).accel_valid); uni_hid_parser_ds4_parse_feature_report(&d, calibration, 37); ds4_input(&d, bytes, 3); assert(snapshot(&d).accel_sequence != first.accel_sequence); uni_hid_parser_native_motion_forget(&d); } static void move_calibration(uni_hid_device_t* d, bool zcm2, bool valid) { uint8_t blob[143] = {0}; const uint8_t lo1[] = {0x0a, 0x24, 0x14}, hi1[] = {0x16, 0x1e, 0x08}; const uint8_t lo2[] = {0x08, 0x16, 0x24}, hi2[] = {0x02, 0x10, 0x1e}; const uint8_t bias1[] = {0x2a, 0x2c, 0x2e}, high1[] = {0x46, 0x50, 0x5a}; const uint8_t bias2[] = {0x26, 0x28, 0x2a}, high2[] = {0x30, 0x38, 0x40}, low2[] = {0x42, 0x4a, 0x52}; for (unsigned i = 0; i < 3; ++i) { int center = zcm2 ? 0 : 0x8000; put16(blob + (zcm2 ? lo2[i] : lo1[i]), center - (valid ? 1000 : 0)); put16(blob + (zcm2 ? hi2[i] : hi1[i]), center + (valid ? 1000 : 0)); put16(blob + (zcm2 ? bias2[i] : bias1[i]), center); put16(blob + (zcm2 ? high2[i] : high1[i]), center + 1000); if (zcm2) put16(blob + low2[i], -1000); } blob[0] = 0x10; uni_hid_parser_psmove_parse_feature_report(d, blob, 49); uint8_t continuation[49] = {0x10, zcm2 ? 0x81 : 1}; memcpy(continuation + 2, blob + 49, 47); uni_hid_parser_psmove_parse_feature_report(d, continuation, 49); if (!zcm2) { continuation[1] = 0x82; memcpy(continuation + 2, blob + 96, 47); uni_hid_parser_psmove_parse_feature_report(d, continuation, 49); } } static void move_provenance(bool zcm2) { uni_hid_device_t d = {0}; d.report_parser.setup = uni_hid_parser_psmove_setup; d.product_id = zcm2 ? 0x0c5e : 0x03d5; uni_hid_parser_psmove_setup(&d); uint8_t report[49] = {1}; report[2] = 0x40; // Cross. report[43] = 1; for (unsigned i = 0; i < 12; ++i) put16(report + 13 + 2 * i, (zcm2 ? 0 : 0x8000) + 1000); uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).report_valid && !snapshot(&d).accel_valid); move_calibration(&d, zcm2, false); report[43] = 2; uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); assert(!snapshot(&d).accel_valid && !snapshot(&d).gyro_valid); move_calibration(&d, zcm2, true); uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); assert(!snapshot(&d).accel_valid); report[43] = 3; uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); uni_native_motion_snapshot_t first = snapshot(&d); assert(first.accel_valid && first.gyro_valid && first.accel_q13[0] == 8192); assert(first.gyro_q10[0] == (zcm2 ? 540 : 480) * 1024); uni_hid_parser_psmove_init_report(&d); uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).gyro_sequence == first.gyro_sequence); report[43] = 2; uni_hid_parser_psmove_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).accel_sequence == first.accel_sequence); uni_hid_parser_psmove_parse_input_report(&d, report, 43); assert(!snapshot(&d).report_valid && snapshot(&d).gyro_sequence == first.gyro_sequence); uni_hid_parser_native_motion_forget(&d); } static void switch_setup(uni_hid_device_t* d, uint8_t type, bool calibrated) { memset(d, 0, sizeof(*d)); d->report_parser.setup = uni_hid_parser_switch_setup; d->conn.interrupt_cid = 0x40; unsigned ready_before = ready_count; uni_hid_parser_switch_setup(d); for (unsigned step = 0; ready_count == ready_before && step < 12; ++step) { assert(command_len >= 12); uint8_t reply[49] = {0x21}; reply[13] = 0x80; reply[14] = command[11]; if (reply[14] == 2) reply[17] = type; if (reply[14] == 0x10) { memcpy(reply + 15, command + 12, 5); // Centered sticks, nonzero spans; no user calibration magic. if (little_endian_read_32(reply, 15) == 0x603d || little_endian_read_32(reply, 15) == 0x6046) memset(reply + 20, 0x80, reply[19]); if (little_endian_read_32(reply, 15) == 0x6020) { for (unsigned i = 0; i < 3; ++i) { put16(reply + 26 + 2 * i, calibrated ? 16384 : 0); put16(reply + 38 + 2 * i, calibrated ? 13371 : 0); } // Truncation with a convincing declared length cannot certify sensors. uni_hid_parser_switch_parse_input_report(d, reply, 22); assert(!snapshot(d).accel_valid); } } uni_hid_parser_switch_parse_input_report(d, reply, sizeof(reply)); } assert(ready_count != ready_before); } static void switch_provenance(uint8_t type, bool calibrated) { uni_hid_device_t d; switch_setup(&d, type, calibrated); uint8_t report[49] = {0x30, 254}; memset(report + 6, 0x80, 6); put16(report + 37, 4096); // Latest accelerometer sample, native X. put16(report + 43, 1000); // Latest gyro sample, native X. uni_hid_parser_switch_parse_input_report(&d, report, sizeof(report)); uni_native_motion_snapshot_t first = snapshot(&d); assert(first.report_tracked && first.report_valid); if (type == 0x0b || !calibrated) { assert(!first.accel_valid && !first.gyro_valid); } else { assert(first.accel_valid && first.gyro_valid && first.accel_q13[2] == -8192); uni_hid_parser_switch_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).accel_sequence == first.accel_sequence); report[1] = 1; uni_hid_parser_switch_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).gyro_sequence != first.gyro_sequence); first = snapshot(&d); report[1] = 0; uni_hid_parser_switch_parse_input_report(&d, report, sizeof(report)); assert(snapshot(&d).gyro_sequence == first.gyro_sequence); } uni_hid_parser_switch_parse_input_report(&d, report, 48); assert(!snapshot(&d).report_valid && snapshot(&d).accel_sequence == first.accel_sequence); uint8_t buttons[12] = {0x3f}; uni_hid_parser_switch_parse_input_report(&d, buttons, sizeof(buttons)); assert(snapshot(&d).report_valid && snapshot(&d).accel_sequence == first.accel_sequence); uni_hid_parser_switch_teardown(&d); assert(!uni_hid_parser_native_motion_snapshot(&d, &first)); } int main(void) { ds4_provenance(); move_provenance(false); move_provenance(true); switch_provenance(3, true); switch_provenance(1, true); switch_provenance(2, true); switch_provenance(3, false); switch_provenance(0x0b, false); uni_hid_device_t sensorless = {0}; uni_native_motion_snapshot_t absent; memset(&absent, 0xff, sizeof(absent)); assert(!uni_hid_parser_native_motion_snapshot(&sensorless, &absent)); assert(!absent.report_tracked && !absent.accel_valid && !absent.gyro_valid && absent.gyro_sequence == 0); puts("Native DS4, Move, Switch motion provenance and sensorless absence passed"); return 0; }