def test_imu_sustain(self): current_time = 0 _mock_time_func = lambda: current_time ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features, imu_time_func=_mock_time_func) # Put into attitude-position mode tracker.imu.use_attitude_position = True # (30, 30, 30), (0, 0, 0) @ 50ms current_time = 0.05 tracker.imu.set_attitude_position(current_time, 30, 30, 30, 0, 0, 0) report = tracker.prepare_for_report_submission() actual = bytes(report)[13:25].hex() expected = '6c266c266c26000000000000' self.assertEqual(actual, expected) # Sustain @ 100ms current_time = 0.10 report = tracker.prepare_for_report_submission() actual = bytes(report)[13:25].hex() expected = '000000000000000000000000' self.assertEqual(actual, expected) # (45, 45, 45), (3, 4, 5) @ 150ms current_time = 0.15 tracker.imu.set_attitude_position(current_time, 45, 45, 45, 3, 4, 5) report = tracker.prepare_for_report_submission() actual = bytes(report)[13:25].hex() expected = '361336133613eb0339058806' self.assertEqual(actual, expected)
def test_ds4key_sign(self): ''' Challenge signing with DS4Key object directly. ''' ds4key_bytes = base64.b64decode(TEST_DS4KEY) ds4key_io = io.BytesIO(ds4key_bytes) ds4id_expected = ds4key_bytes[:0x310] ds4key = ds4.DS4Key(ds4key_io) challenge = os.urandom(256) response = ds4key.sign_challenge(challenge) resp_bytes = bytes(response) sig = resp_bytes[:0x100] ds4id = resp_bytes[0x100:] self.assertEqual(ds4id.hex(), ds4id_expected.hex(), 'DS4ID mismatch.') ds4key_key = RSA.import_key(TEST_DS4KEY_JUST_KEY) ds4key_pss = pss.new(ds4key_key) sha = SHA256.new(challenge) try: ds4key_pss.verify(sha, sig) except ValueError as e: self.fail(f'Response verification failed. ({str(e)})')
def test_imu_set_linear_acceleration(self): ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.imu.set_linear_acceleration(1, 1, 1) report = tracker.prepare_for_report_submission() actual = bytes(report)[13:25].hex() # (0, 0, 0), (8192, 8192, 8192) expected = '000000000000002000200020' self.assertEqual(actual, expected)
def test_imu_set_angular_velocity(self): ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.imu.set_angular_velocity(90, 90, 90) report = tracker.prepare_for_report_submission() actual = bytes(report)[13:25].hex() # 1000 * 90(deg) / 61 = approx. 1475 # (1475, 1475, 1475), (0, 0, 0) expected = 'c305c305c305000000000000' self.assertEqual(actual, expected)
def test_input_touchpad_one(self): ''' One frame 2 point touch. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.touch.queue_touchpad((100, 200), (200, 300)) report = tracker.prepare_for_report_submission() actual = bytes(report)[33:61].hex() expected = TP_SET_ONE.hex() self.assertEqual(actual, expected)
def test_input_touchpad_nohold_after_release(self): ''' Touch shouldn't be held when they are released. Instead they should be invalidated. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.touch.queue_touchpad((100, 200), (200, 300)) tracker.prepare_for_report_submission() tracker.touch.queue_touchpad() report = tracker.prepare_for_report_submission() actual = bytes(report)[33:61].hex() expected = TP_SET_ONE_INVALIDATED.hex() self.assertEqual(actual, expected)
def test_tracker_edit_context(self): ''' Report editing context and buffer swapping. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) with tracker.start_modify_report() as report: report: ds4.InputReport report.set_button(ds4.ButtonType.ps, True) with tracker.input_report_lock: tracker.swap_buffer_nolock() tracker.sync_buffer_nolock() actual = tracker.input_report_submitting_buf[5:8].hex() actual_next = tracker.input_report_submitting_buf[5:8].hex() expected = '080001' self.assertEqual(actual, expected) self.assertEqual(actual_next, expected)
def test_input_touchpad_release_both(self): ''' Release both points will cause both points to be invalidated. Only checks the invalidation states. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.touch.queue_touchpad((100, 200), (200, 300)) tracker.prepare_for_report_submission() tracker.touch.queue_touchpad() report = tracker.prepare_for_report_submission() tp_report = bytes(report)[33:61] actual = (bool(tp_report[2] & 0x80), bool(tp_report[6] & 0x80)) expected = (True, True) self.assertEqual(actual, expected)
def test_input_touchpad_nohold(self): ''' Touch shouldn't be held when there are actual new points. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.touch.queue_touchpad((230, 125), (500, 100)) tracker.prepare_for_report_submission() tracker.touch.queue_touchpad((100, 200), (200, 300)) report = tracker.prepare_for_report_submission() # this should do nothing to the touchpad frames now actual = bytes(report)[33:61].hex() expected_bytes = bytearray(TP_SET_ONE) # Frame sequence should be 2 now expected_bytes[1] = 2 expected = expected_bytes.hex() # Exactly as before with empty unused frames self.assertEqual(actual, expected)
def test_input_touchpad_nohold_after_release_2(self): ''' Invalidated touches will continue to be available despite invalidated. This mimics official DS4 behavior. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) # Initial frame tracker.touch.queue_touchpad((100, 200), (200, 300)) tracker.prepare_for_report_submission() # Release - This is one packet despite all points are invalidated. tracker.touch.queue_touchpad() tracker.prepare_for_report_submission() # Sustain the invalidated packet. No value should be incrementing. report = tracker.prepare_for_report_submission() actual = bytes(report)[33:61].hex() expected = TP_SET_ONE_INVALIDATED.hex() self.assertEqual(actual, expected)
def test_input_touchpad_hold(self): ''' Not updating the points will cause them to be held at position with frame seq incrementing. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) tracker.touch.queue_touchpad((100, 200), (200, 300)) # Hold for 2 frames tracker.prepare_for_report_submission() tracker.prepare_for_report_submission() report = tracker.prepare_for_report_submission() actual = bytes(report)[33:61].hex() expected_bytes = bytearray(TP_SET_ONE) # Frame sequence should be 3 now expected_bytes[1] = 3 expected = expected_bytes.hex() self.assertEqual(actual, expected)
def test_input_touchpad_touch_seq_inc(self): ''' Touch sequence should be increased when different touches are registered. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) # Initial frame tracker.touch.queue_touchpad((100, 200), (200, 300)) tracker.prepare_for_report_submission() # Release - This is one packet despite all points are invalidated. tracker.touch.queue_touchpad() tracker.prepare_for_report_submission() # Second frame with 2 different points tracker.touch.queue_touchpad((100, 200), (200, 300)) report = tracker.prepare_for_report_submission() actual = bytes(report)[33:61].hex() expected = TP_SET_ONE_AFTER_RELEASE.hex() self.assertEqual(actual, expected)
def test_tracker_auth_full(self): ''' Authentication with state tracker (API-level DS4 protocol mockup). ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) ds4key = ds4.DS4Key(ds4key_io) ds4id_expected = ds4key_io.getvalue()[:0x310] features = ds4.FeatureConfiguration() tracker = ds4.DS4StateTracker(ds4key, features) challenge = io.BytesIO(b'\x00' * 256) page = 0 tracker.auth.reset() while challenge.tell() < 256: data = bytearray(56) challenge.readinto(data) packet = b'\xf0\x01' + page.to_bytes(1, 'big') + b'\x00' + bytes(data) crc = zlib.crc32(packet) packet += crc.to_bytes(4, 'little') assert len(packet) == 64, 'Test case bug: invalid challenge packet length.' packet_io = io.BytesIO(packet) tracker.auth.set_challenge(packet_io) page += 1 wait_count = 0 while True: response_io = io.BytesIO() tracker.auth.get_status(response_io) response = response_io.getvalue() self.assertEqual(len(response), 16, 'Wrong GetAuthStatus length.') self.assertEqual(response[0], 0xf2, 'Invalid GetAuthStatus magic.') self.assertEqual(response[1], 0x1, 'Wrong GetAuthStatus sequence counter.') self.assertIn(response[2], (0x01, 0x00), 'Invalid auth status.') if response[2] == 0x01: wait_count += 1 time.sleep(0.05) if wait_count > 100: self.fail('Auth took too long to respond.') elif response[2] == 0x00: break remaining = 0x410 page = 0 response_io_full = io.BytesIO() while remaining > 0: response_io = io.BytesIO() tracker.auth.get_response(response_io) response = response_io.getvalue() self.assertEqual(len(response), 64, 'Wrong GetResponse length.') self.assertEqual(response[0], 0xf1, 'Invalid GetResponse magic.') self.assertEqual(response[1], 0x1, 'Wrong GetResponse sequence counter.') self.assertEqual(response[2], page, 'Wrong GetResponse page counter.') data = response[4:4+min(56, remaining)] response_io_full.write(data) remaining = max(0, remaining - 56) page += 1 response_full = response_io_full.getvalue() assert len(response_full) == 0x410, 'Test case bug: wrong full response length.' sig = response_full[:0x100] ds4id = response_full[0x100:] self.assertEqual(ds4id.hex(), ds4id_expected.hex(), 'DS4ID mismatch.') ds4key_key = RSA.import_key(TEST_DS4KEY_JUST_KEY) ds4key_pss = pss.new(ds4key_key) sha = SHA256.new(challenge.getvalue()) try: ds4key_pss.verify(sha, sig) except ValueError as e: self.fail(f'Response verification failed. ({str(e)})')
def test_ds4key_load(self): ''' DS4Key loading. ''' ds4key_io = io.BytesIO(base64.b64decode(TEST_DS4KEY)) _ds4key = ds4.DS4Key(ds4key_io)