Files
FlipperDroid/flipper/flipperdroid_app.c
sssnake be81a92d44 Initial commit — FlipperDroid v0.1.0-poc
KernelSU module + Flipper Zero FAP that bridges both devices into a
unified pentesting platform over USB CDC serial / BT rfcomm.

Android side: bridge daemon, WebUI (:8089), bind mount namespace
isolation stealth engine. Flipper side: proper FAP with 4-view GUI,
GPIO/SubGHz/IR/file command handlers, async event streaming.
2026-03-31 21:26:58 -07:00

352 lines
11 KiB
C

/**
* FlipperDroid — main application entry point
* Handles GUI, input, lifecycle. Workers in fd_bridge.c.
*/
#include "fd_app.h"
#include <stdio.h>
#include <stdarg.h>
#define TAG "FlipperDroid"
/* ---- GPIO pin table ---- */
static const struct {
uint8_t id;
const GpioPin* pin;
const char* name;
} gpio_defs[] = {
{2, &gpio_ext_pa2, "A2"},
{3, &gpio_ext_pa3, "A3"},
{4, &gpio_ext_pa4, "A4"},
{6, &gpio_ext_pa6, "A6"},
{7, &gpio_ext_pa7, "A7"},
{13, &gpio_ext_pb2, "B2"},
{14, &gpio_ext_pb3, "B3"},
{15, &gpio_ext_pb13, "B13"},
{16, &gpio_ext_pb14, "B14"},
{17, &gpio_ext_pc0, "C0"},
{18, &gpio_ext_pc1, "C1"},
{19, &gpio_ext_pc3, "C3"},
};
#define GPIO_DEF_COUNT (sizeof(gpio_defs) / sizeof(gpio_defs[0]))
/* ---- Logging ---- */
void fd_log(FdApp* app, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
char buf[FD_LOG_COLS + 1];
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
furi_mutex_acquire(app->mutex, FuriWaitForever);
uint8_t idx = (app->log.head + app->log.count) % FD_LOG_LINES;
if(app->log.count < FD_LOG_LINES) {
app->log.count++;
} else {
app->log.head = (app->log.head + 1) % FD_LOG_LINES;
}
strncpy(app->log.lines[idx], buf, FD_LOG_COLS);
app->log.lines[idx][FD_LOG_COLS] = '\0';
furi_mutex_release(app->mutex);
FURI_LOG_I(TAG, "%s", buf);
}
/* ---- Draw ---- */
void fd_draw_callback(Canvas* canvas, void* ctx) {
FdApp* app = (FdApp*)ctx;
furi_mutex_acquire(app->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
switch(app->current_view) {
case FdViewStatus: {
/* Title bar */
canvas_draw_str(canvas, 0, 10, "FlipperDroid");
canvas_set_font(canvas, FontSecondary);
/* Connection status */
if(app->connected) {
canvas_draw_str(canvas, 72, 10, "[LINKED]");
} else {
canvas_draw_str(canvas, 68, 10, "[WAITING]");
}
/* Stats */
char buf[40];
canvas_draw_str(canvas, 0, 22, "Status:");
canvas_draw_str(canvas, 44, 22, app->connected ? "Connected" : "Listening on USB");
snprintf(buf, sizeof(buf), "RX: %lu TX: %lu ERR: %lu",
app->rx_count, app->tx_count, app->err_count);
canvas_draw_str(canvas, 0, 32, buf);
/* Subsystem status */
snprintf(buf, sizeof(buf), "SubGHz: %s %lu Hz",
app->subghz_rx_active ? "RX" : "idle",
app->subghz_freq);
canvas_draw_str(canvas, 0, 42, buf);
snprintf(buf, sizeof(buf), "IR: %s GPIO: %u pins",
app->ir_rx_active ? "RX" : "idle", app->gpio_count);
canvas_draw_str(canvas, 0, 52, buf);
/* Nav hint */
canvas_draw_str(canvas, 0, 63, "<GPIO SubGHz> OK:Log Bk:Exit");
break;
}
case FdViewGpio: {
canvas_draw_str(canvas, 0, 10, "GPIO Pins");
canvas_set_font(canvas, FontSecondary);
for(uint8_t i = 0; i < app->gpio_count && i < 12; i++) {
uint8_t row = i / 4;
uint8_t col = i % 4;
uint8_t x = col * 32;
uint8_t y = 20 + row * 14;
char buf[12];
FdGpioEntry* g = &app->gpio[i];
if(i == app->selected_gpio) {
canvas_draw_box(canvas, x, y - 8, 30, 12);
canvas_set_color(canvas, ColorWhite);
}
if(g->initialized) {
snprintf(buf, sizeof(buf), "%s:%c", g->name, g->value ? 'H' : 'L');
} else {
snprintf(buf, sizeof(buf), "%s:--", g->name);
}
canvas_draw_str(canvas, x + 1, y, buf);
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(canvas, 0, 63, "<Stat ^v:Sel OK:Read >SG");
break;
}
case FdViewSubghz: {
canvas_draw_str(canvas, 0, 10, "Sub-GHz");
canvas_set_font(canvas, FontSecondary);
char buf[40];
snprintf(buf, sizeof(buf), "Freq: %lu Hz", app->subghz_freq);
canvas_draw_str(canvas, 0, 22, buf);
snprintf(buf, sizeof(buf), "RSSI: %.1f dBm", (double)app->subghz_last_rssi);
canvas_draw_str(canvas, 0, 32, buf);
canvas_draw_str(canvas, 0, 42, app->subghz_rx_active ? "State: RECEIVING" : "State: IDLE");
snprintf(buf, sizeof(buf), "Packets: %lu", app->rx_count);
canvas_draw_str(canvas, 0, 52, buf);
canvas_draw_str(canvas, 0, 63, "<GPIO OK:RX Tog >Log");
break;
}
case FdViewLog: {
canvas_draw_str(canvas, 0, 10, "Log");
canvas_set_font(canvas, FontKeyboard);
for(uint8_t i = 0; i < app->log.count && i < 6; i++) {
uint8_t idx = (app->log.head + i) % FD_LOG_LINES;
canvas_draw_str(canvas, 0, 20 + i * 8, app->log.lines[idx]);
}
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 63, "<SubGHz Bk:Exit");
break;
}
}
furi_mutex_release(app->mutex);
}
/* ---- Input ---- */
void fd_input_callback(InputEvent* event, void* ctx) {
FdApp* app = (FdApp*)ctx;
furi_message_queue_put(app->input_queue, event, FuriWaitForever);
}
/* ---- Alloc / Free ---- */
FdApp* fd_app_alloc(void) {
FdApp* app = malloc(sizeof(FdApp));
memset(app, 0, sizeof(FdApp));
app->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->gui = furi_record_open(RECORD_GUI);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->current_view = FdViewStatus;
app->running = true;
/* Init GPIO table */
app->gpio_count = GPIO_DEF_COUNT;
for(uint8_t i = 0; i < GPIO_DEF_COUNT; i++) {
app->gpio[i].id = gpio_defs[i].id;
app->gpio[i].pin = gpio_defs[i].pin;
app->gpio[i].name = gpio_defs[i].name;
app->gpio[i].initialized = false;
app->gpio[i].output = false;
app->gpio[i].value = false;
}
/* Create viewport */
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, fd_draw_callback, app);
view_port_input_callback_set(app->view_port, fd_input_callback, app);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void fd_app_free(FdApp* app) {
/* Stop workers */
app->running = false;
if(app->rx_thread) {
furi_thread_join(app->rx_thread);
furi_thread_free(app->rx_thread);
}
if(app->event_thread) {
furi_thread_join(app->event_thread);
furi_thread_free(app->event_thread);
}
/* Cleanup SubGHz */
if(app->subghz_rx_active) {
furi_hal_subghz_idle();
app->subghz_rx_active = false;
}
/* Reset GPIO pins to analog (safe default) */
for(uint8_t i = 0; i < app->gpio_count; i++) {
if(app->gpio[i].initialized) {
furi_hal_gpio_init(app->gpio[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
}
/* Remove GUI */
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_message_queue_free(app->input_queue);
furi_mutex_free(app->mutex);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_GUI);
free(app);
}
/* ---- Main loop ---- */
int32_t fd_app_run(FdApp* app) {
fd_log(app, "Bridge starting...");
/* Launch RX worker — listens for commands on USB CDC */
app->rx_thread = furi_thread_alloc_ex("FdRx", 4096, fd_rx_worker, app);
furi_thread_start(app->rx_thread);
/* Launch event worker — pushes async events to phone */
app->event_thread = furi_thread_alloc_ex("FdEvt", 2048, fd_event_worker, app);
furi_thread_start(app->event_thread);
fd_log(app, "Listening on USB CDC...");
notification_message(app->notifications, &sequence_blink_start_blue);
/* Input loop */
InputEvent event;
while(app->running) {
if(furi_message_queue_get(app->input_queue, &event, 100) == FuriStatusOk) {
if(event.type != InputTypePress && event.type != InputTypeRepeat) continue;
switch(event.key) {
case InputKeyBack:
app->running = false;
break;
case InputKeyRight:
if(app->current_view == FdViewStatus) app->current_view = FdViewSubghz;
else if(app->current_view == FdViewGpio) app->current_view = FdViewSubghz;
else if(app->current_view == FdViewSubghz) app->current_view = FdViewLog;
break;
case InputKeyLeft:
if(app->current_view == FdViewLog) app->current_view = FdViewSubghz;
else if(app->current_view == FdViewSubghz) app->current_view = FdViewGpio;
else if(app->current_view == FdViewGpio) app->current_view = FdViewStatus;
break;
case InputKeyOk:
if(app->current_view == FdViewStatus) {
app->current_view = FdViewLog;
} else if(app->current_view == FdViewGpio) {
/* Read selected GPIO pin */
uint8_t sel = app->selected_gpio;
if(sel < app->gpio_count) {
if(!app->gpio[sel].initialized) {
furi_hal_gpio_init(
app->gpio[sel].pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
app->gpio[sel].initialized = true;
}
app->gpio[sel].value = furi_hal_gpio_read(app->gpio[sel].pin);
fd_log(app, "GPIO %s = %s",
app->gpio[sel].name,
app->gpio[sel].value ? "HIGH" : "LOW");
}
} else if(app->current_view == FdViewSubghz) {
/* Toggle SubGHz RX */
if(app->subghz_rx_active) {
furi_hal_subghz_idle();
app->subghz_rx_active = false;
fd_log(app, "SubGHz RX stopped");
} else if(app->subghz_freq > 0) {
furi_hal_subghz_idle();
furi_hal_subghz_set_frequency(app->subghz_freq);
furi_hal_subghz_rx();
app->subghz_rx_active = true;
fd_log(app, "SubGHz RX on %lu Hz", app->subghz_freq);
}
}
break;
case InputKeyUp:
if(app->current_view == FdViewGpio && app->selected_gpio > 0)
app->selected_gpio--;
break;
case InputKeyDown:
if(app->current_view == FdViewGpio && app->selected_gpio < app->gpio_count - 1)
app->selected_gpio++;
break;
default:
break;
}
view_port_update(app->view_port);
}
/* Periodic screen refresh */
view_port_update(app->view_port);
}
notification_message(app->notifications, &sequence_blink_stop);
fd_log(app, "Bridge stopped");
return 0;
}
/* ---- Entry point ---- */
int32_t flipperdroid_app(void* p) {
UNUSED(p);
FdApp* app = fd_app_alloc();
fd_app_run(app);
fd_app_free(app);
return 0;
}