diff options
author | Ruslan Sayfutdinov <ruslan@sayfutdinov.com> | 2022-12-08 16:45:30 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-09 03:45:30 +1100 |
commit | 85ee55ff3becd29e1590376c3462c6a99c976fa0 (patch) | |
tree | 36ab093ee44919070ac260f8aaf2770871d14910 /quantum | |
parent | e06f50c489e02068465dfb3e5b3c7473006e6273 (diff) |
Detect host OS based on USB fingerprint (#18463)
Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Nick Brassel <nick@tzarc.org>
Diffstat (limited to 'quantum')
-rw-r--r-- | quantum/os_detection.c | 129 | ||||
-rw-r--r-- | quantum/os_detection.h | 38 | ||||
-rw-r--r-- | quantum/os_detection/tests/os_detection.cpp | 164 | ||||
-rw-r--r-- | quantum/os_detection/tests/rules.mk | 5 | ||||
-rw-r--r-- | quantum/os_detection/tests/testlist.mk | 1 |
5 files changed, 337 insertions, 0 deletions
diff --git a/quantum/os_detection.c b/quantum/os_detection.c new file mode 100644 index 0000000000..b1511afb14 --- /dev/null +++ b/quantum/os_detection.c @@ -0,0 +1,129 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "os_detection.h" + +#include <string.h> + +#ifdef OS_DETECTION_DEBUG_ENABLE +# include "eeconfig.h" +# include "eeprom.h" +# include "print.h" + +# define STORED_USB_SETUPS 50 +# define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE + +uint16_t usb_setups[STORED_USB_SETUPS]; +#endif + +#ifdef OS_DETECTION_ENABLE +struct setups_data_t { + uint8_t count; + uint8_t cnt_02; + uint8_t cnt_04; + uint8_t cnt_ff; + uint16_t last_wlength; + os_variant_t detected_os; +}; + +struct setups_data_t setups_data = { + .count = 0, + .cnt_02 = 0, + .cnt_04 = 0, + .cnt_ff = 0, + .detected_os = OS_UNSURE, +}; + +// Some collected sequences of wLength can be found in tests. +void make_guess(void) { + if (setups_data.count < 3) { + return; + } + if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) { + setups_data.detected_os = OS_WINDOWS; + return; + } + if (setups_data.count == setups_data.cnt_ff) { + // Linux has 3 packets with 0xFF. + setups_data.detected_os = OS_LINUX; + return; + } + if (setups_data.count == 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff == 1 && setups_data.cnt_02 == 2) { + setups_data.detected_os = OS_MACOS; + return; + } + if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) { + // iOS and iPadOS don't have the last 0xFF packet. + setups_data.detected_os = OS_IOS; + return; + } + if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) { + // This is actually PS5. + setups_data.detected_os = OS_LINUX; + return; + } + if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) { + // This is actually Quest 2 or Nintendo Switch. + setups_data.detected_os = OS_LINUX; + return; + } +} + +void process_wlength(const uint16_t w_length) { +# ifdef OS_DETECTION_DEBUG_ENABLE + usb_setups[setups_data.count] = w_length; +# endif + setups_data.count++; + setups_data.last_wlength = w_length; + if (w_length == 0x2) { + setups_data.cnt_02++; + } else if (w_length == 0x4) { + setups_data.cnt_04++; + } else if (w_length == 0xFF) { + setups_data.cnt_ff++; + } + make_guess(); +} + +os_variant_t detected_host_os(void) { + return setups_data.detected_os; +} + +void erase_wlength_data(void) { + memset(&setups_data, 0, sizeof(setups_data)); +} +#endif // OS_DETECTION_ENABLE + +#ifdef OS_DETECTION_DEBUG_ENABLE +void print_stored_setups(void) { +# ifdef CONSOLE_ENABLE + uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET); + for (uint16_t i = 0; i < cnt; ++i) { + uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); + xprintf("i: %d, wLength: 0x%02X\n", i, eeprom_read_word(addr)); + } +# endif +} + +void store_setups_in_eeprom(void) { + eeprom_update_byte(EEPROM_USER_OFFSET, setups_data.count); + for (uint16_t i = 0; i < setups_data.count; ++i) { + uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t); + eeprom_update_word(addr, usb_setups[i]); + } +} + +#endif // OS_DETECTION_DEBUG_ENABLE diff --git a/quantum/os_detection.h b/quantum/os_detection.h new file mode 100644 index 0000000000..e643dcd27f --- /dev/null +++ b/quantum/os_detection.h @@ -0,0 +1,38 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdint.h> + +#ifdef OS_DETECTION_ENABLE +typedef enum { + OS_UNSURE, + OS_LINUX, + OS_WINDOWS, + OS_MACOS, + OS_IOS, +} os_variant_t; + +void process_wlength(const uint16_t w_length); +os_variant_t detected_host_os(void); +void erase_wlength_data(void); +#endif + +#ifdef OS_DETECTION_DEBUG_ENABLE +void print_stored_setups(void); +void store_setups_in_eeprom(void); +#endif diff --git a/quantum/os_detection/tests/os_detection.cpp b/quantum/os_detection/tests/os_detection.cpp new file mode 100644 index 0000000000..102349852e --- /dev/null +++ b/quantum/os_detection/tests/os_detection.cpp @@ -0,0 +1,164 @@ +/* Copyright 2022 Ruslan Sayfutdinov (@KapJI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gtest/gtest.h" + +extern "C" { +#include "os_detection.h" +} + +class OsDetectionTest : public ::testing::Test { + protected: + void SetUp() override { + erase_wlength_data(); + } +}; + +os_variant_t check_sequence(const std::vector<uint16_t> &w_lengths) { + for (auto &w_length : w_lengths) { + process_wlength(w_length); + } + return detected_host_os(); +} + +/* Some collected data. + +ChibiOS: +Windows 10: [FF, FF, 4, 24, 4, 24, 4, FF, 24, FF, 4, FF, 24, 4, 24, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (another host): [FF, FF, 4, 24, 4, 24, 4, 24, 4, 24, 4, 24] +macOS 12.5: [2, 24, 2, 28, FF] +iOS/iPadOS 15.6: [2, 24, 2, 28] +Linux (including Android, Raspberry Pi and WebOS TV): [FF, FF, FF] +PS5: [2, 4, 2, 28, 2, 24] +Nintendo Switch: [82, FF, 40, 40, FF, 40, 40, FF, 40, 40, FF, 40, 40, FF, 40, 40] +Quest 2: [FF, FF, FF, FE, FF, FE, FF, FE, FF, FE, FF] + +LUFA: +Windows 10 (first connect): [12, FF, FF, 4, 10, FF, FF, FF, 4, 10, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (subsequent connect): [FF, FF, 4, 10, FF, 4, FF, 10, FF, 20A, 20A, 20A, 20A, 20A, 20A] +Windows 10 (another host): [FF, FF, 4, 10, 4, 10] +macOS: [2, 10, 2, E, FF] +iOS/iPadOS: [2, 10, 2, E] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, E, 2, 10] +Nintendo Switch: [82, FF, 40, 40, FF, 40, 40] + +V-USB: +Windows 10: [FF, FF, 4, E, FF] +Windows 10 (another host): [FF, FF, 4, E, 4] +macOS: [2, E, 2, E, FF] +iOS/iPadOS: [2, E, 2, E] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, E, 2] +Nintendo Switch: [82, FF, 40, 40] +Quest 2: [FF, FF, FF, FE] + +Common parts: +Windows: [..., FF, FF, 4, ...] +macOS: [2, _, 2, _, FF] +iOS/iPadOS: [2, _, 2, _] +Linux: [FF, FF, FF] +PS5: [2, 4, 2, _, 2, ...] +Nintendo Switch: [82, FF, 40, 40, ...] +Quest 2: [FF, FF, FF, FE, ...] +*/ +TEST_F(OsDetectionTest, TestLinux) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosMacos) { + EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestLufaMacos) { + EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestVusbMacos) { + EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE, 0xFF}), OS_MACOS); +} + +TEST_F(OsDetectionTest, TestChibiosIos) { + EXPECT_EQ(check_sequence({0x2, 0x24, 0x2, 0x28}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestLufaIos) { + EXPECT_EQ(check_sequence({0x2, 0x10, 0x2, 0xE}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestVusbIos) { + EXPECT_EQ(check_sequence({0x2, 0xE, 0x2, 0xE}), OS_IOS); +} + +TEST_F(OsDetectionTest, TestChibiosWindows10) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0xFF, 0x24, 0xFF, 0x4, 0xFF, 0x24, 0x4, 0x24, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestChibiosWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24, 0x4, 0x24}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10) { + EXPECT_EQ(check_sequence({0x12, 0xFF, 0xFF, 0x4, 0x10, 0xFF, 0xFF, 0xFF, 0x4, 0x10, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0xFF, 0x4, 0xFF, 0x10, 0xFF, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A, 0x20A}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestLufaWindows10_3) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0x10, 0x4, 0x10}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestVusbWindows10) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0xFF}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestVusbWindows10_2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0x4, 0xE, 0x4}), OS_WINDOWS); +} + +TEST_F(OsDetectionTest, TestChibiosPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0x28, 0x2, 0x24}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestLufaPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2, 0x10}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbPs5) { + EXPECT_EQ(check_sequence({0x2, 0x4, 0x2, 0xE, 0x2}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestLufaNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbNintendoSwitch) { + EXPECT_EQ(check_sequence({0x82, 0xFF, 0x40, 0x40}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestChibiosQuest2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF}), OS_LINUX); +} + +TEST_F(OsDetectionTest, TestVusbQuest2) { + EXPECT_EQ(check_sequence({0xFF, 0xFF, 0xFF, 0xFE}), OS_LINUX); +} diff --git a/quantum/os_detection/tests/rules.mk b/quantum/os_detection/tests/rules.mk new file mode 100644 index 0000000000..9bfe373f46 --- /dev/null +++ b/quantum/os_detection/tests/rules.mk @@ -0,0 +1,5 @@ +os_detection_DEFS := -DOS_DETECTION_ENABLE + +os_detection_SRC := \ + $(QUANTUM_PATH)/os_detection/tests/os_detection.cpp \ + $(QUANTUM_PATH)/os_detection.c diff --git a/quantum/os_detection/tests/testlist.mk b/quantum/os_detection/tests/testlist.mk new file mode 100644 index 0000000000..405a7b82d5 --- /dev/null +++ b/quantum/os_detection/tests/testlist.mk @@ -0,0 +1 @@ +TEST_LIST += os_detection |