mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-12-19 01:03:22 +00:00
28eb4d1060
Not full refactoring, only small issues is fixed and moved all plugins to furi mutex instead of valuemutex Many small issues was found and fixed due mutex upgrade OFW removed 60 lines of code and it was painful
456 lines
15 KiB
C
456 lines
15 KiB
C
#include <furi.h>
|
|
#include <furi_hal.h>
|
|
#include <gui/gui.h>
|
|
#include <input/input.h>
|
|
#include <notification/notification_messages.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <nrf24.h>
|
|
#include <toolbox/stream/file_stream.h>
|
|
|
|
#define LOGITECH_MAX_CHANNEL 85
|
|
#define COUNT_THRESHOLD 2
|
|
#define DEFAULT_SAMPLE_TIME 8000
|
|
#define MAX_ADDRS 100
|
|
#define MAX_CONFIRMED 32
|
|
|
|
#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff"
|
|
#define NRFSNIFF_APP_FILENAME "addresses.txt"
|
|
#define TAG "nrfsniff"
|
|
|
|
typedef enum {
|
|
EventTypeTick,
|
|
EventTypeKey,
|
|
} EventType;
|
|
|
|
typedef struct {
|
|
EventType type;
|
|
InputEvent input;
|
|
} PluginEvent;
|
|
|
|
typedef struct {
|
|
FuriMutex* mutex;
|
|
} PluginState;
|
|
|
|
char rate_text_fmt[] = "Transfer rate: %dMbps";
|
|
char sample_text_fmt[] = "Sample Time: %d ms";
|
|
char channel_text_fmt[] = "Channel: %d Sniffing: %s";
|
|
char preamble_text_fmt[] = "Preamble: %02X";
|
|
char sniff_text_fmt[] = "Found: %d Unique: %u";
|
|
char addresses_header_text[] = "Address,rate";
|
|
char sniffed_address_fmt[] = "%s,%d";
|
|
char rate_text[46];
|
|
char channel_text[38];
|
|
char sample_text[32];
|
|
char preamble_text[14];
|
|
char sniff_text[38];
|
|
char sniffed_address[14];
|
|
|
|
uint8_t target_channel = 0;
|
|
uint32_t found_count = 0;
|
|
uint32_t unique_saved_count = 0;
|
|
uint32_t sample_time = DEFAULT_SAMPLE_TIME;
|
|
uint8_t target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
|
uint8_t target_preamble[] = {0xAA, 0x00};
|
|
uint8_t sniffing_state = false;
|
|
char top_address[12];
|
|
|
|
uint8_t candidates[MAX_ADDRS][5] = {0}; // last 100 sniffed addresses
|
|
uint32_t counts[MAX_ADDRS];
|
|
uint8_t confirmed[MAX_CONFIRMED][5] = {0}; // first 32 confirmed addresses
|
|
uint8_t confirmed_idx = 0;
|
|
uint32_t total_candidates = 0;
|
|
uint32_t candidate_idx = 0;
|
|
|
|
static int get_addr_index(uint8_t* addr, uint8_t addr_size) {
|
|
for(uint32_t i = 0; i < total_candidates; i++) {
|
|
uint8_t* arr_item = candidates[i];
|
|
if(!memcmp(arr_item, addr, addr_size)) return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int get_highest_idx() {
|
|
uint32_t highest = 0;
|
|
int highest_idx = 0;
|
|
for(uint32_t i = 0; i < total_candidates; i++) {
|
|
if(counts[i] > highest) {
|
|
highest = counts[i];
|
|
highest_idx = i;
|
|
}
|
|
}
|
|
|
|
return highest_idx;
|
|
}
|
|
|
|
// if array is full, start over from beginning
|
|
static void insert_addr(uint8_t* addr, uint8_t addr_size) {
|
|
if(candidate_idx >= MAX_ADDRS) candidate_idx = 0;
|
|
|
|
memcpy(candidates[candidate_idx], addr, addr_size);
|
|
counts[candidate_idx] = 1;
|
|
if(total_candidates < MAX_ADDRS) total_candidates++;
|
|
candidate_idx++;
|
|
}
|
|
|
|
static void render_callback(Canvas* const canvas, void* ctx) {
|
|
furi_assert(ctx);
|
|
const PluginState* plugin_state = ctx;
|
|
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
|
|
|
uint8_t rate = 2;
|
|
char sniffing[] = "Yes";
|
|
|
|
// border around the edge of the screen
|
|
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
if(target_rate == 0) rate = 1;
|
|
|
|
if(!sniffing_state) strcpy(sniffing, "No");
|
|
|
|
snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate);
|
|
snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel, sniffing);
|
|
snprintf(sample_text, sizeof(sample_text), sample_text_fmt, (int)sample_time);
|
|
//snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]);
|
|
snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, found_count, unique_saved_count);
|
|
snprintf(
|
|
sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate);
|
|
canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text);
|
|
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, sample_text);
|
|
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, channel_text);
|
|
//canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text);
|
|
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, sniff_text);
|
|
canvas_draw_str_aligned(canvas, 30, 50, AlignLeft, AlignBottom, addresses_header_text);
|
|
canvas_draw_str_aligned(canvas, 30, 60, AlignLeft, AlignBottom, sniffed_address);
|
|
|
|
furi_mutex_release(plugin_state->mutex);
|
|
}
|
|
|
|
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
|
|
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
}
|
|
|
|
static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
|
memset(out, 0, size * 2);
|
|
for(int i = 0; i < size; i++)
|
|
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
|
}
|
|
|
|
static bool save_addr_to_file(
|
|
Storage* storage,
|
|
uint8_t* data,
|
|
uint8_t size,
|
|
NotificationApp* notification) {
|
|
size_t file_size = 0;
|
|
uint8_t linesize = 0;
|
|
char filepath[42] = {0};
|
|
char addrline[14] = {0};
|
|
char ending[4];
|
|
uint8_t* file_contents;
|
|
uint8_t rate = 1;
|
|
Stream* stream = file_stream_alloc(storage);
|
|
|
|
if(target_rate == 8) rate = 2;
|
|
snprintf(ending, sizeof(ending), ",%d\n", rate);
|
|
hexlify(data, size, addrline);
|
|
strcat(addrline, ending);
|
|
linesize = strlen(addrline);
|
|
strcpy(filepath, NRFSNIFF_APP_PATH_FOLDER);
|
|
strcat(filepath, "/");
|
|
strcat(filepath, NRFSNIFF_APP_FILENAME);
|
|
stream_seek(stream, 0, StreamOffsetFromStart);
|
|
|
|
// check if address already exists in file
|
|
if(file_stream_open(stream, filepath, FSAM_READ_WRITE, FSOM_OPEN_APPEND)) {
|
|
bool found = false;
|
|
file_size = stream_size(stream);
|
|
stream_seek(stream, 0, StreamOffsetFromStart);
|
|
if(file_size > 0) {
|
|
file_contents = malloc(file_size + 1);
|
|
memset(file_contents, 0, file_size + 1);
|
|
if(stream_read(stream, file_contents, file_size) > 0) {
|
|
char* line = strtok((char*)file_contents, "\n");
|
|
|
|
while(line != NULL) {
|
|
if(!memcmp(line, addrline, 12)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
}
|
|
free(file_contents);
|
|
}
|
|
|
|
if(found) {
|
|
FURI_LOG_I(TAG, "Address exists in file. Ending save process.");
|
|
stream_free(stream);
|
|
return false;
|
|
} else {
|
|
if(stream_write(stream, (uint8_t*)addrline, linesize) != linesize) {
|
|
FURI_LOG_I(TAG, "Failed to write bytes to file stream.");
|
|
stream_free(stream);
|
|
return false;
|
|
} else {
|
|
FURI_LOG_I(TAG, "Found a new address: %s", addrline);
|
|
FURI_LOG_I(TAG, "Save successful!");
|
|
|
|
notification_message(notification, &sequence_success);
|
|
|
|
stream_free(stream);
|
|
unique_saved_count++;
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
FURI_LOG_I(TAG, "Cannot open file \"%s\"", filepath);
|
|
stream_free(stream);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void alt_address(uint8_t* addr, uint8_t* altaddr) {
|
|
uint8_t macmess_hi_b[4];
|
|
uint32_t macmess_hi;
|
|
uint8_t macmess_lo;
|
|
uint8_t preserved;
|
|
uint8_t tmpaddr[5];
|
|
|
|
// swap bytes
|
|
for(int i = 0; i < 5; i++) tmpaddr[i] = addr[4 - i];
|
|
|
|
// get address into 32-bit and 8-bit variables
|
|
memcpy(macmess_hi_b, tmpaddr, 4);
|
|
macmess_lo = tmpaddr[4];
|
|
|
|
macmess_hi = bytes_to_int32(macmess_hi_b, true);
|
|
|
|
//preserve lowest bit from hi to shift to low
|
|
preserved = macmess_hi & 1;
|
|
macmess_hi >>= 1;
|
|
macmess_lo >>= 1;
|
|
macmess_lo = (preserved << 7) | macmess_lo;
|
|
int32_to_bytes(macmess_hi, macmess_hi_b, true);
|
|
memcpy(tmpaddr, macmess_hi_b, 4);
|
|
tmpaddr[4] = macmess_lo;
|
|
|
|
// swap bytes back
|
|
for(int i = 0; i < 5; i++) altaddr[i] = tmpaddr[4 - i];
|
|
}
|
|
|
|
static bool previously_confirmed(uint8_t* addr) {
|
|
bool found = false;
|
|
for(int i = 0; i < MAX_CONFIRMED; i++) {
|
|
if(!memcmp(confirmed[i], addr, 5)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static void wrap_up(Storage* storage, NotificationApp* notification) {
|
|
uint8_t ch;
|
|
uint8_t addr[5];
|
|
uint8_t altaddr[5];
|
|
char trying[12];
|
|
int idx;
|
|
uint8_t rate = 0;
|
|
if(target_rate == 8) rate = 2;
|
|
|
|
nrf24_set_idle(nrf24_HANDLE);
|
|
|
|
while(true) {
|
|
idx = get_highest_idx();
|
|
if(counts[idx] < COUNT_THRESHOLD) break;
|
|
|
|
counts[idx] = 0;
|
|
memcpy(addr, candidates[idx], 5);
|
|
hexlify(addr, 5, trying);
|
|
FURI_LOG_I(TAG, "trying address %s", trying);
|
|
ch = nrf24_find_channel(nrf24_HANDLE, addr, addr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
|
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
|
if(ch > LOGITECH_MAX_CHANNEL) {
|
|
alt_address(addr, altaddr);
|
|
hexlify(altaddr, 5, trying);
|
|
FURI_LOG_I(TAG, "trying alternate address %s", trying);
|
|
ch = nrf24_find_channel(
|
|
nrf24_HANDLE, altaddr, altaddr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
|
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
|
memcpy(addr, altaddr, 5);
|
|
}
|
|
|
|
if(ch <= LOGITECH_MAX_CHANNEL) {
|
|
hexlify(addr, 5, top_address);
|
|
found_count++;
|
|
save_addr_to_file(storage, addr, 5, notification);
|
|
if(confirmed_idx < MAX_CONFIRMED) memcpy(confirmed[confirmed_idx++], addr, 5);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void clear_cache() {
|
|
found_count = 0;
|
|
unique_saved_count = 0;
|
|
confirmed_idx = 0;
|
|
candidate_idx = 0;
|
|
target_channel = 2;
|
|
total_candidates = 0;
|
|
memset(candidates, 0, sizeof(candidates));
|
|
memset(counts, 0, sizeof(counts));
|
|
memset(confirmed, 0, sizeof(confirmed));
|
|
}
|
|
|
|
static void start_sniffing() {
|
|
nrf24_init_promisc_mode(nrf24_HANDLE, target_channel, target_rate);
|
|
}
|
|
|
|
int32_t nrfsniff_app(void* p) {
|
|
UNUSED(p);
|
|
uint8_t address[5] = {0};
|
|
uint32_t start = 0;
|
|
hexlify(address, 5, top_address);
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
|
PluginState* plugin_state = malloc(sizeof(PluginState));
|
|
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
if(!plugin_state->mutex) {
|
|
furi_message_queue_free(event_queue);
|
|
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
|
free(plugin_state);
|
|
return 255;
|
|
}
|
|
|
|
nrf24_init();
|
|
|
|
// Set system callbacks
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, render_callback, plugin_state);
|
|
view_port_input_callback_set(view_port, input_callback, event_queue);
|
|
|
|
// Open GUI and register view_port
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER);
|
|
|
|
PluginEvent event;
|
|
for(bool processing = true; processing;) {
|
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
|
|
|
|
if(event_status == FuriStatusOk) {
|
|
// press events
|
|
if(event.type == EventTypeKey) {
|
|
if(event.input.type == InputTypePress ||
|
|
(event.input.type == InputTypeLong && event.input.key == InputKeyBack)) {
|
|
switch(event.input.key) {
|
|
case InputKeyUp:
|
|
// toggle rate 1/2Mbps
|
|
if(!sniffing_state) {
|
|
if(target_rate == 0)
|
|
target_rate = 8;
|
|
else
|
|
target_rate = 0;
|
|
}
|
|
break;
|
|
case InputKeyDown:
|
|
// toggle preamble
|
|
if(!sniffing_state) {
|
|
if(target_preamble[0] == 0x55)
|
|
target_preamble[0] = 0xAA;
|
|
else
|
|
target_preamble[0] = 0x55;
|
|
|
|
nrf24_set_src_mac(nrf24_HANDLE, target_preamble, 2);
|
|
}
|
|
break;
|
|
case InputKeyRight:
|
|
// increment channel
|
|
//if(!sniffing_state && target_channel <= LOGITECH_MAX_CHANNEL)
|
|
// target_channel++;
|
|
sample_time += 500;
|
|
break;
|
|
case InputKeyLeft:
|
|
// decrement channel
|
|
//if(!sniffing_state && target_channel > 0) target_channel--;
|
|
if(sample_time > 500) sample_time -= 500;
|
|
break;
|
|
case InputKeyOk:
|
|
// toggle sniffing
|
|
sniffing_state = !sniffing_state;
|
|
if(sniffing_state) {
|
|
clear_cache();
|
|
start_sniffing();
|
|
start = furi_get_tick();
|
|
} else
|
|
wrap_up(storage, notification);
|
|
break;
|
|
case InputKeyBack:
|
|
if(event.input.type == InputTypeLong) processing = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(sniffing_state) {
|
|
if(nrf24_sniff_address(nrf24_HANDLE, 5, address)) {
|
|
int idx;
|
|
uint8_t* top_addr;
|
|
if(!previously_confirmed(address)) {
|
|
idx = get_addr_index(address, 5);
|
|
if(idx == -1)
|
|
insert_addr(address, 5);
|
|
else
|
|
counts[idx]++;
|
|
|
|
top_addr = candidates[get_highest_idx()];
|
|
hexlify(top_addr, 5, top_address);
|
|
}
|
|
}
|
|
|
|
if(furi_get_tick() - start >= sample_time) {
|
|
target_channel++;
|
|
if(target_channel > LOGITECH_MAX_CHANNEL) target_channel = 2;
|
|
{
|
|
wrap_up(storage, notification);
|
|
start_sniffing();
|
|
}
|
|
|
|
start = furi_get_tick();
|
|
}
|
|
}
|
|
|
|
view_port_update(view_port);
|
|
furi_mutex_release(plugin_state->mutex);
|
|
}
|
|
|
|
clear_cache();
|
|
sample_time = DEFAULT_SAMPLE_TIME;
|
|
target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
|
sniffing_state = false;
|
|
nrf24_deinit();
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
furi_record_close(RECORD_STORAGE);
|
|
view_port_free(view_port);
|
|
furi_message_queue_free(event_queue);
|
|
furi_mutex_free(plugin_state->mutex);
|
|
free(plugin_state);
|
|
|
|
return 0;
|
|
}
|