MX 2 лет назад
Родитель
Сommit
7a3e7b8b44

+ 16 - 0
non_catalog_apps/hid_file_transfer/.vscode/settings.json

@@ -0,0 +1,16 @@
+{
+    "C_Cpp.default.includePath": [
+        "${workspaceFolder}/../unleashed-firmware/**"
+    ],
+    "files.associations": {
+        "docker-compose*.yml": "dockercompose",
+        "Dockerfile*": "dockerfile",
+        "docker-compose.*.yml": "dockercompose",
+        "Caddyfile*": "caddyfile",
+        "filelogger.h": "c",
+        "file_stream.h": "c",
+        "usbif.h": "c",
+        "elements.h": "c",
+        "menu.h": "c"
+    }
+}

+ 17 - 0
non_catalog_apps/hid_file_transfer/application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="hidtransfer",
+    name="HID File Transfer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="hidtransfer_app",
+    cdefines=["APP_HID_TRANSFER"],
+    requires=[
+        "gui",
+        "dialogs",
+    ],
+    stack_size=6 * 1024,
+    order=20,
+    fap_icon="icons/hid_10px.png",
+    fap_category="USB",
+    fap_icon_assets="assets",
+    fap_libs=["assets"],
+)

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
non_catalog_apps/hid_file_transfer/assets/arrows.pixil


BIN
non_catalog_apps/hid_file_transfer/assets/left_14.png


BIN
non_catalog_apps/hid_file_transfer/assets/right_14.png


+ 4 - 0
non_catalog_apps/hid_file_transfer/constants.h

@@ -0,0 +1,4 @@
+
+
+#define TAG "HIDT_main"
+#define TAG_IF "HIDT_IF"

+ 108 - 0
non_catalog_apps/hid_file_transfer/filelogger.c

@@ -0,0 +1,108 @@
+#include "filelogger.h"
+#include <core/check.h>
+#include <core/mutex.h>
+#include <furi_hal.h>
+#include <storage/storage.h>
+
+#define FURI_LOG_LEVEL_DEFAULT FuriLogLevelInfo
+
+typedef struct {
+    FuriLogLevel log_level;
+    FuriLogPuts puts;
+    FuriLogTimestamp timestamp;
+    FuriMutex* mutex;
+    File *logFile;
+    Storage *storage;
+} FuriLogParams;
+
+static FuriLogParams furi_log;
+
+void writeToLog(const char *msg) {
+    furi_hal_console_puts(msg);
+
+    // Storage *s = furi_record_open(RECORD_STORAGE);
+    // File *f = storage_file_alloc(s);
+    // storage_file_open(f, "/any/log.txt", FSAM_READ_WRITE, FSOM_OPEN_APPEND);
+    storage_file_write(furi_log.logFile, msg, strlen(msg));
+    // storage_file_close(f);
+    // storage_file_free(f);
+    // furi_record_close(RECORD_STORAGE);
+}
+
+
+
+void file_log_init() {
+    // Set default logging parameters
+    furi_log.log_level = FURI_LOG_LEVEL_DEFAULT;
+    furi_log.puts = writeToLog;
+    furi_log.timestamp = furi_get_tick;
+    furi_log.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    furi_log.storage = furi_record_open(RECORD_STORAGE);
+    furi_log.logFile = storage_file_alloc(furi_log.storage);    
+    storage_file_open(furi_log.logFile, "/any/log.txt", FSAM_READ_WRITE, FSOM_OPEN_APPEND);
+}
+
+
+void file_log_deinit() {
+    storage_file_close(furi_log.logFile);
+    storage_file_free(furi_log.logFile);
+    furi_record_close(RECORD_STORAGE);
+    furi_mutex_free(furi_log.mutex);
+}
+
+void file_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) {
+    if(level <= furi_log_get_level() &&
+       furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) {
+        FuriString* string;
+        string = furi_string_alloc();
+
+        const char* log_letter = " ";
+        switch(level) {
+        case FuriLogLevelError:
+            log_letter = "E";
+            break;
+        case FuriLogLevelWarn:
+            log_letter = "W";
+            break;
+        case FuriLogLevelInfo:
+            log_letter = "I";
+            break;
+        case FuriLogLevelDebug:
+            log_letter = "D";
+            break;
+        case FuriLogLevelTrace:
+            log_letter = "T";
+            break;
+        default:
+            break;
+        }
+
+        // Timestamp
+        furi_string_printf(
+            string,
+            "%lu [%s][%s] ",
+            furi_log.timestamp(),
+            log_letter,
+            tag);
+        furi_log.puts(furi_string_get_cstr(string));
+        furi_string_reset(string);
+
+        va_list args;
+        va_start(args, format);
+        furi_string_vprintf(string, format, args);
+        va_end(args);
+
+        furi_log.puts(furi_string_get_cstr(string));
+        furi_string_free(string);
+
+        furi_log.puts("\r\n");
+
+        furi_mutex_release(furi_log.mutex);
+    }
+}
+
+
+void file_log_set_timestamp(FuriLogTimestamp timestamp) {
+    furi_assert(timestamp);
+    furi_log.timestamp = timestamp;
+}

+ 70 - 0
non_catalog_apps/hid_file_transfer/filelogger.h

@@ -0,0 +1,70 @@
+/**
+ * @file log.h
+ * Furi Logging system
+ */
+#pragma once
+
+#include <core/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Initialize logging */
+void file_log_init();
+void file_log_deinit();
+
+/** Print log record
+ * 
+ * @param level 
+ * @param tag 
+ * @param format 
+ * @param ... 
+ */
+void file_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...)
+    _ATTRIBUTE((__format__(__printf__, 3, 4)));
+
+// /** Set log level
+//  *
+//  * @param[in]  level  The level
+//  */
+// void file_log_set_level(FuriLogLevel level);
+
+// /** Get log level
+//  *
+//  * @return     The furi log level.
+//  */
+// FuriLogLevel file_log_get_level();
+
+// /** Set log output callback
+//  *
+//  * @param[in]  puts  The puts callback
+//  */
+// void file_log_set_puts(FuriLogPuts puts);
+
+/** Set timestamp callback
+ *
+ * @param[in]  timestamp  The timestamp callback
+ */
+void file_log_set_timestamp(FuriLogTimestamp timestamp);
+
+/** Log methods
+ *
+ * @param      tag     The application tag
+ * @param      format  The format
+ * @param      ...     VA Args
+ */
+#define FILE_LOG_E(tag, format, ...) \
+    file_log_print_format(FuriLogLevelError, tag, format, ##__VA_ARGS__)
+#define FILE_LOG_W(tag, format, ...) \
+    file_log_print_format(FuriLogLevelWarn, tag, format, ##__VA_ARGS__)
+#define FILE_LOG_I(tag, format, ...) \
+    file_log_print_format(FuriLogLevelInfo, tag, format, ##__VA_ARGS__)
+#define FILE_LOG_D(tag, format, ...) \
+    file_log_print_format(FuriLogLevelDebug, tag, format, ##__VA_ARGS__)
+#define FILE_LOG_T(tag, format, ...) \
+    file_log_print_format(FuriLogLevelTrace, tag, format, ##__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif

+ 6 - 0
non_catalog_apps/hid_file_transfer/hidtransfer_icons.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include <gui/icon.h>
+
+extern const Icon I_left_14;
+extern const Icon I_right_14;

BIN
non_catalog_apps/hid_file_transfer/icons/hid_10px.png


+ 470 - 0
non_catalog_apps/hid_file_transfer/main.c

@@ -0,0 +1,470 @@
+#include <furi.h>
+#include <furi_hal.h>
+
+#include "usbif.h"
+#include <gui/gui.h>
+#include <gui/elements.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_box.h>
+#include <dialogs/dialogs.h>
+
+#include <storage/storage.h>
+#include <stream/stream.h>
+#include <stream/buffered_file_stream.h>
+#include <toolbox/stream/file_stream.h>
+#include "filelogger.h"
+#include "constants.h"
+#include <assets_icons.h>
+#include "hidtransfer_icons.h"
+
+#define HEX_VIEWER_APP_PATH_FOLDER "/any"
+#define HEX_VIEWER_APP_EXTENSION "*"
+
+#define HEX_VIEWER_BYTES_PER_LINE 4u
+#define HEX_VIEWER_LINES_ON_SCREEN 4u
+#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE)
+
+#define VIEW_DISPATCHER_MENU 0
+#define VIEW_DISPATCHER_SEND 1
+#define VIEW_DISPATCHER_SEND_SINGLE_THREADED 3
+#define VIEW_DISPATCHER_RECEIVE 2
+#define VIEW_DISPATCHER_POPUP 3
+#define VIEW_DISPATCHER_DEBUG_SEND 99
+#define VIEW_DISPATCHER_DEBUG_RECEIVE 98
+
+typedef struct {
+    uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
+    uint32_t file_offset;
+    uint32_t file_read_bytes;
+    uint32_t file_size;
+    Stream* stream;
+    bool mode; // Print address or content
+} DataTransferAppModel;
+
+typedef struct {
+    DataTransferAppModel* model;
+
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+    Storage* storage;
+} DataTransferApp;
+
+typedef enum MessageType {
+    MessageMetadata = 0,
+    MessageFullPayload = 1,
+    MessagePartPayload = 2
+} MessageType;
+
+// 3 byte
+typedef struct {
+    uint32_t counter; // 22 bit LSB
+    MessageType messageType; // 2 bit
+} MessageHeader;
+
+// 5 byte + len(fileName)
+typedef struct {
+    MessageHeader header;
+    uint32_t fileSize;
+    const char* fileName;
+} FileMetadataMessage;
+
+// max. 64 byte, see payloadLength
+typedef struct {
+    // 3 byte
+    MessageHeader header;
+    // 61 byte
+    uint8_t* payload;
+} FullPayloadMessage;
+
+// max. 64 byte, see payloadLength
+typedef struct {
+    // 3 byte
+    MessageHeader header;
+    uint8_t payloadLength;
+    // 61 byte
+    uint8_t* payload;
+} PartPayloadMessage;
+
+TextBox* textBoxReceive;
+Popup* popup;
+DataTransferApp* app;
+void openMenu(void* bla);
+
+static void* parseMessage(MessageType* outMsg, void* msgBuffer) {
+    uint32_t header = 0;
+    memcpy(&header, msgBuffer, 3);
+    FURI_LOG_D(TAG, "Parse message, header: %lu", header);
+
+    MessageType msgType = header & 3;
+    uint32_t counter = header >> 2;
+
+    *outMsg = msgType;
+
+    if(msgType == MessageMetadata) {
+        FURI_LOG_D(TAG, "Parse Metadata message");
+        furi_check(counter == 0);
+        uint32_t fileSize;
+
+        int strl = strlen(msgBuffer + 7);
+        char* fileName = malloc(strl + 1);
+
+        memcpy(&fileSize, msgBuffer + 3, sizeof(fileSize));
+        strncpy(fileName, msgBuffer + 7, strl);
+
+        FileMetadataMessage* msg = malloc(sizeof(FileMetadataMessage));
+        memset(msg, 0, sizeof(FileMetadataMessage));
+
+        *msg = (FileMetadataMessage){
+            .header = {.counter = counter, .messageType = MessageMetadata},
+            .fileName = fileName,
+            .fileSize = fileSize};
+        return msg;
+    }
+
+    FURI_LOG_E(TAG, "Tried to parse unknown msg! %d", msgType);
+    furi_check(false);
+}
+
+static DataTransferApp* dataTransferApp_alloc() {
+    FURI_LOG_D(TAG, "alloc");
+    DataTransferApp* instance = malloc(sizeof(DataTransferApp));
+
+    instance->model = malloc(sizeof(DataTransferAppModel));
+    memset(instance->model, 0x0, sizeof(DataTransferAppModel));
+
+    instance->view_dispatcher = view_dispatcher_alloc();
+
+    instance->gui = furi_record_open(RECORD_GUI);
+    view_dispatcher_enable_queue(instance->view_dispatcher);
+    view_dispatcher_attach_to_gui(
+        instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+
+    instance->storage = furi_record_open(RECORD_STORAGE);
+
+    return instance;
+}
+
+static void dataTransferApp_free(DataTransferApp* instance) {
+    furi_record_close(RECORD_STORAGE);
+
+    view_dispatcher_free(instance->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+
+    if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
+
+    free(instance->model);
+    free(instance);
+}
+
+void sendMessage(uint8_t* msg) {
+    MessageHeader* header = (MessageHeader*)msg;
+
+    uint32_t c = header->counter;
+    c = c << 2 | header->messageType;
+
+    void* sendBuf = malloc(64);
+    memset(sendBuf, 0x0, 64);
+    memcpy(sendBuf, &c, 3);
+
+    if(header->messageType == MessageMetadata) {
+        FileMetadataMessage* m = (FileMetadataMessage*)msg;
+        memcpy(sendBuf + 3, &(m->fileSize), 4);
+        strncpy(sendBuf + 7, m->fileName, 64 - 7);
+        //memcpy(sendBuf + 7, m->fileName, strlen(m->fileName));
+    } else if(header->messageType == MessageFullPayload) {
+        FullPayloadMessage* m = (FullPayloadMessage*)msg;
+        memcpy(sendBuf + 3, m->payload, 61);
+    } else if(header->messageType == MessagePartPayload) {
+        PartPayloadMessage* m = (PartPayloadMessage*)msg;
+        memcpy(sendBuf + 3, &(m->payloadLength), 1);
+        memcpy(sendBuf + 4, m->payload, m->payloadLength);
+    }
+
+    sendBulkData(sendBuf, 64);
+}
+
+void sendHeader(uint32_t fileSize, const char* fileName) {
+    FileMetadataMessage md = {
+        .header = {.counter = 0, .messageType = MessageMetadata},
+        .fileSize = fileSize,
+        .fileName = fileName};
+
+    uint8_t* bytePtr = (uint8_t*)&md;
+    sendMessage(bytePtr);
+}
+
+static void dispatch_view(void* contextd, uint32_t index) {
+    DataTransferApp* context = (DataTransferApp*)contextd;
+
+    if(index == VIEW_DISPATCHER_SEND || index == VIEW_DISPATCHER_SEND_SINGLE_THREADED) {
+        initializeSendingData(index == VIEW_DISPATCHER_SEND ? NUM_OF_INTERFACES : 1);
+
+        FuriString* browser_path;
+        browser_path = furi_string_alloc();
+
+        FuriString* selected_path;
+        selected_path = furi_string_alloc();
+
+        furi_string_set(browser_path, HEX_VIEWER_APP_PATH_FOLDER);
+
+        DialogsFileBrowserOptions browser_options;
+        dialog_file_browser_set_basic_options(&browser_options, HEX_VIEWER_APP_EXTENSION, NULL);
+        browser_options.hide_ext = false;
+        browser_options.base_path = furi_string_get_cstr(browser_path);
+
+        DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+        bool res =
+            dialog_file_browser_show(dialogs, selected_path, browser_path, &browser_options);
+
+        furi_record_close(RECORD_DIALOGS);
+        if(!res) {
+            FURI_LOG_I(TAG, "No file selected");
+            furi_string_free(browser_path);
+            furi_string_free(selected_path);
+            view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_MENU);
+            return;
+        }
+
+        view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_SEND);
+
+        size_t idx = furi_string_search_rchar(selected_path, '/');
+        FuriString* path_copy = furi_string_alloc();
+        furi_string_set_n(
+            path_copy, selected_path, idx + 1, furi_string_size(selected_path) - idx - 1);
+
+        const char* path = furi_string_get_cstr(selected_path);
+
+        Stream* fs = buffered_file_stream_alloc(context->storage);
+        buffered_file_stream_open(fs, path, FSAM_READ, FSOM_OPEN_EXISTING);
+
+        size_t file_size = stream_size(fs);
+
+        sendHeader(file_size, furi_string_get_cstr(path_copy));
+        furi_string_free(path_copy);
+
+        uint32_t sent = 0;
+        uint8_t data[64];
+        memset(data, 0, 64);
+
+        uint32_t msgCounter = 1;
+
+        while(sent < file_size) {
+            memset(data, 0, 61);
+            uint8_t to_read = 61;
+            if(file_size - sent < 61) {
+                to_read = file_size - sent;
+            }
+            sent += to_read;
+            stream_read(fs, data, to_read);
+
+            if(to_read == 61) {
+                FullPayloadMessage msg = {
+                    .header = {.counter = msgCounter, .messageType = MessageFullPayload},
+                    .payload = data,
+                };
+                sendMessage((uint8_t*)&msg);
+            } else {
+                PartPayloadMessage msg = {
+                    .header = {.counter = msgCounter, .messageType = MessagePartPayload},
+                    .payloadLength = to_read,
+                    .payload = data};
+                sendMessage((uint8_t*)&msg);
+            }
+
+            msgCounter++;
+
+            //furi_hal_hid_u2f_send_response(data, 64);
+        }
+
+        FURI_LOG_D(TAG, "Finished sending packet");
+        stopSendingData();
+
+        buffered_file_stream_close(fs);
+        free(fs);
+        furi_string_free(browser_path);
+        furi_string_free(selected_path);
+        view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_MENU);
+    } else if(index == VIEW_DISPATCHER_RECEIVE) {
+        view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_RECEIVE);
+        FuriString* textBoxText = furi_string_alloc_printf("Waiting for file...");
+        text_box_set_text(textBoxReceive, furi_string_get_cstr(textBoxText));
+
+        FuriMessageQueue* queue = initializeReceivingData();
+
+        ThreadMessage threadMsg;
+        FileMetadataMessage* metadataMsg;
+        furi_check(furi_message_queue_get(queue, &threadMsg, FuriWaitForever) == FuriStatusOk);
+        MessageType msgtype;
+
+        void* parsedMsg = parseMessage(&msgtype, threadMsg.dataPointer);
+        FURI_LOG_D(TAG, "received %d", msgtype);
+        //furi_check(msgtype == MessageMetadata);
+        metadataMsg = (FileMetadataMessage*)parsedMsg;
+
+        furi_string_printf(textBoxText, "Receiving %s...", metadataMsg->fileName);
+        text_box_set_text(textBoxReceive, furi_string_get_cstr(textBoxText));
+
+        Stream* fs = buffered_file_stream_alloc(context->storage);
+        storage_common_mkdir(context->storage, "/any/HIDTransfer");
+
+        FuriString* filePath =
+            furi_string_alloc_printf("/any/HIDTransfer/%s.raw", metadataMsg->fileName);
+        if(!buffered_file_stream_open(
+               fs, furi_string_get_cstr(filePath), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+            FURI_LOG_D(TAG, "Could not open filestream");
+            furi_string_printf(textBoxText, "Could not open filestream");
+            text_box_set_text(textBoxReceive, furi_string_get_cstr(textBoxText));
+            furi_delay_ms(1500);
+            furi_crash("FSEr");
+        }
+
+        // write metadata message to file
+        stream_write(fs, threadMsg.dataPointer, 64);
+
+        uint32_t expectedNumMsgs = (uint32_t)ceil(metadataMsg->fileSize / 61.0);
+        uint32_t increamentStep = (uint32_t)floor(0.1 * expectedNumMsgs);
+
+        int finished = 0;
+        uint32_t numMsgs = 0;
+
+        while(true) {
+            ThreadMessage msg;
+            furi_check(furi_message_queue_get(queue, &msg, FuriWaitForever) == FuriStatusOk);
+            if(msg.dataPointer == NULL) {
+                finished += 1;
+                if(finished == NUM_OF_INTERFACES) {
+                    break;
+                }
+                continue;
+            }
+
+            numMsgs += 1;
+            if(numMsgs % increamentStep == 0) {
+                int percent = (int)ceil(numMsgs / (double)expectedNumMsgs * 100);
+                furi_string_printf(
+                    textBoxText, "Receiving %s...\n%d%%", metadataMsg->fileName, percent);
+                text_box_set_text(textBoxReceive, furi_string_get_cstr(textBoxText));
+            }
+
+            //FURI_LOG_D(TAG, "after Queue");
+            stream_write(fs, msg.dataPointer, 64);
+            free(msg.dataPointer);
+        }
+
+        buffered_file_stream_close(fs);
+        free(fs);
+        free((void*)metadataMsg->fileName);
+        free(metadataMsg);
+        furi_string_free(filePath);
+
+        int missingMsgs = expectedNumMsgs - numMsgs;
+
+        if(missingMsgs != 0) {
+            furi_string_cat_printf(
+                textBoxText, "\nCAUTION: %d messages are missing.", missingMsgs);
+            FuriString* txtMsg = furi_string_alloc_printf("%d messages are missing.", missingMsgs);
+            popup_set_icon(popup, 4, 19, &I_Warning_30x23);
+            popup_set_header(popup, "CAUTION", 53, 19, AlignLeft, AlignCenter);
+            popup_set_text(popup, furi_string_get_cstr(txtMsg), 39, 28, AlignLeft, AlignTop);
+            popup_set_callback(popup, &openMenu);
+            view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_POPUP);
+        } else {
+            furi_string_cat_printf(textBoxText, "\nFinished receiving!");
+            text_box_set_text(textBoxReceive, furi_string_get_cstr(textBoxText));
+            furi_delay_ms(5000);
+            view_dispatcher_switch_to_view(context->view_dispatcher, VIEW_DISPATCHER_MENU);
+        }
+
+        furi_string_free(textBoxText);
+    } else if(index == VIEW_DISPATCHER_DEBUG_SEND) {
+        uint8_t buf[64];
+        for(int i = 0; i < 64; i++) {
+            buf[i] = i + 1;
+        }
+        sendViaEP(buf, 0);
+    } else if(index == VIEW_DISPATCHER_DEBUG_RECEIVE) {
+        uint8_t buf[64];
+        receiveFromEP(buf, 0);
+        FURI_LOG_D(TAG, "01: %d, last: %d", buf[0], buf[63]);
+    }
+}
+
+bool eventCallback() {
+    return false;
+}
+
+void openMenu(void* bla) {
+    UNUSED(bla);
+    view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_DISPATCHER_MENU);
+}
+
+bool inputCallback(InputEvent* event, void* context) {
+    UNUSED(context);
+    UNUSED(event);
+    FURI_LOG_D(TAG, "Back button pressend on sending view");
+    return true;
+}
+
+int32_t hidtransfer_app() {
+    furi_log_set_level(FuriLogLevelDebug);
+    furi_hal_console_enable();
+
+    FURI_LOG_D(TAG, "APP STARTED");
+
+    FuriHalUsbInterface* mode = furi_hal_usb_get_config();
+    furi_hal_usb_set_config(getUsbHidBulk(), NULL);
+
+    app = dataTransferApp_alloc();
+    Menu* mainMenu = menu_alloc();
+    menu_add_item(
+        mainMenu,
+        "Send Client",
+        &I_right_14,
+        VIEW_DISPATCHER_SEND_SINGLE_THREADED,
+        dispatch_view,
+        app);
+    menu_add_item(mainMenu, "Send File", &I_right_14, VIEW_DISPATCHER_SEND, dispatch_view, app);
+    menu_add_item(
+        mainMenu, "Receive File", &I_left_14, VIEW_DISPATCHER_RECEIVE, dispatch_view, app);
+    //menu_add_item(mainMenu, "Debug Send", &I_Pin_arrow_right_9x7, VIEW_DISPATCHER_DEBUG_SEND, dispatch_view, app);
+    //menu_add_item(mainMenu, "Debug Receive", &I_Pin_arrow_left_9x7, VIEW_DISPATCHER_DEBUG_RECEIVE, dispatch_view, app);
+
+    // Sending View
+    TextBox* textBoxSend = text_box_alloc();
+    text_box_set_text(textBoxSend, "Sending file...");
+    text_box_set_font(textBoxSend, TextBoxFontText);
+    View* textBoxView = text_box_get_view(textBoxSend);
+    view_set_input_callback(textBoxView, inputCallback);
+
+    // Receive View
+    textBoxReceive = text_box_alloc();
+    text_box_set_text(textBoxReceive, "Receiveing file...");
+    text_box_set_font(textBoxReceive, TextBoxFontText);
+    View* textBoxRecView = text_box_get_view(textBoxReceive);
+    view_set_input_callback(textBoxRecView, inputCallback);
+
+    // Popup
+    popup = popup_alloc();
+    popup_disable_timeout(popup);
+
+    view_dispatcher_add_view(app->view_dispatcher, VIEW_DISPATCHER_MENU, menu_get_view(mainMenu));
+    view_dispatcher_add_view(app->view_dispatcher, VIEW_DISPATCHER_SEND, textBoxView);
+    view_dispatcher_add_view(app->view_dispatcher, VIEW_DISPATCHER_RECEIVE, textBoxRecView);
+    view_dispatcher_add_view(app->view_dispatcher, VIEW_DISPATCHER_POPUP, popup_get_view(popup));
+    view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_DISPATCHER_MENU);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, eventCallback);
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    dataTransferApp_free(app);
+
+    furi_hal_usb_set_config(mode, NULL);
+    menu_free(mainMenu);
+    text_box_free(textBoxSend);
+    text_box_free(textBoxReceive);
+    popup_free(popup);
+
+    return 0;
+}

+ 741 - 0
non_catalog_apps/hid_file_transfer/usbif.c

@@ -0,0 +1,741 @@
+#include "furi_hal_version.h"
+#include "furi_hal_usb_i.h"
+#include "furi_hal_usb_hid_u2f.h"
+#include <furi.h>
+#include "usb.h"
+#include <usb_hid.h>
+#include "usbd_core.h"
+#include "usb_std.h"
+#include "filelogger.h"
+
+#include "usbif.h"
+#include <storage/storage.h>
+#include "constants.h"
+#include <hid_usage_desktop.h>
+
+static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc.");
+static const struct usb_string_descriptor dev_prod_desc = USB_STRING_DESC("U2F Token");
+#define HID_PAGE_FIDO 0xF1D0
+#define HID_FIDO_U2F 0x01
+#define HID_FIDO_INPUT 0x20
+#define HID_FIDO_OUTPUT 0x21
+
+#define HID_EP_IN 0x81
+#define HID_EP_OUT 0x01
+
+static const uint8_t endpoint_type = USB_EPTYPE_INTERRUPT;
+
+struct HidIadDescriptor {
+    struct usb_interface_descriptor hid;
+    struct usb_hid_descriptor hid_desc;
+    struct usb_endpoint_descriptor hid_ep_in;
+    struct usb_endpoint_descriptor hid_ep_out;
+
+    struct usb_interface_descriptor hid2;
+    struct usb_hid_descriptor hid_desc2;
+    struct usb_endpoint_descriptor hid_ep_in2;
+    struct usb_endpoint_descriptor hid_ep_out2;
+
+    struct usb_interface_descriptor hid3;
+    struct usb_hid_descriptor hid_desc3;
+    struct usb_endpoint_descriptor hid_ep_in3;
+    struct usb_endpoint_descriptor hid_ep_out3;
+
+    struct usb_interface_descriptor hid4;
+    struct usb_hid_descriptor hid_desc4;
+    struct usb_endpoint_descriptor hid_ep_in4;
+    struct usb_endpoint_descriptor hid_ep_out4;
+
+    struct usb_interface_descriptor hid5;
+    struct usb_hid_descriptor hid_desc5;
+    struct usb_endpoint_descriptor hid_ep_in5;
+    struct usb_endpoint_descriptor hid_ep_out5;
+
+    struct usb_interface_descriptor hid6;
+    struct usb_hid_descriptor hid_desc6;
+    struct usb_endpoint_descriptor hid_ep_in6;
+    struct usb_endpoint_descriptor hid_ep_out6;
+
+    // struct usb_interface_descriptor hid7;
+    // struct usb_hid_descriptor hid_desc7;
+    // struct usb_endpoint_descriptor hid_ep_in7;
+    // struct usb_endpoint_descriptor hid_ep_out7;
+
+    // struct usb_interface_descriptor hid8;
+    // struct usb_hid_descriptor hid_desc8;
+    // struct usb_endpoint_descriptor hid_ep_in8;
+    // struct usb_endpoint_descriptor hid_ep_out8;
+} __attribute__((packed));
+
+struct HidConfigDescriptor {
+    struct usb_config_descriptor config;
+    struct HidIadDescriptor iad_0;
+} __attribute__((packed));
+
+static const struct usb_device_descriptor hid_u2f_device_desc = {
+    .bLength = sizeof(struct usb_device_descriptor),
+    .bDescriptorType = USB_DTYPE_DEVICE,
+    .bcdUSB = VERSION_BCD(2, 0, 0),
+    .bDeviceClass = 0,
+    .bDeviceSubClass = 0,
+    .bDeviceProtocol = 0,
+    .bMaxPacketSize0 = USB_EP0_SIZE,
+    .idVendor = 0x485, // 0x0483,
+    .idProduct = 0xFFFF, //0x5741,
+    .bcdDevice = VERSION_BCD(1, 0, 0),
+    .iManufacturer = UsbDevManuf,
+    .iProduct = UsbDevProduct,
+    .iSerialNumber = 0,
+    .bNumConfigurations = 1,
+};
+
+/* HID report: FIDO U2F */
+static const uint8_t hid_u2f_report_desc[] = {
+    HID_RI_USAGE_PAGE(16, 0xff00),
+    HID_USAGE(HID_FIDO_U2F),
+    HID_COLLECTION(HID_APPLICATION_COLLECTION),
+    HID_USAGE(HID_FIDO_INPUT),
+    HID_LOGICAL_MINIMUM(0x00),
+    HID_RI_LOGICAL_MAXIMUM(16, 0xFF),
+    HID_REPORT_SIZE(8),
+    HID_REPORT_COUNT(HID_U2F_PACKET_LEN),
+    HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
+    HID_USAGE(HID_FIDO_OUTPUT),
+    HID_LOGICAL_MINIMUM(0x00),
+    HID_RI_LOGICAL_MAXIMUM(16, 0xFF),
+    HID_REPORT_SIZE(8),
+    HID_REPORT_COUNT(HID_U2F_PACKET_LEN),
+    HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
+    HID_END_COLLECTION,
+};
+
+// static const uint8_t hid_u2f_report_desc[] = {
+// 0x06, 0x00, 0xFF,  // Usage Page (Vendor Defined 0xFF00)
+// 0x09, 0x01,        // Usage (0x01)
+// 0xA1, 0x01,        // Collection (Application)
+// 0x19, 0x01,        //   Usage Minimum (0x01)
+// 0x29, 0x40,        //   Usage Maximum (0x40)
+// 0x15, 0x00,        //   Logical Minimum (0)
+// 0x26, 0xFF, 0x00,  //   Logical Maximum (255)
+// 0x75, 0x08,        //   Report Size (8)
+// 0x95, 0x01,        //   Report Count (1)
+// 0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+// 0x19, 0x01,        //   Usage Minimum (0x01)
+// 0x29, 0x40,        //   Usage Maximum (0x40)
+// 0x15, 0x00,        //   Logical Minimum (0)
+// 0x26, 0xFF, 0x00,  //   Logical Maximum (255)
+// 0x75, 0x08,        //   Report Size (8)
+// 0x95, 0x01,        //   Report Count (1)
+// 0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+// 0xC0,              // End Collection
+// };
+
+/* Device configuration descriptor */
+static const struct HidConfigDescriptor
+    hid_u2f_cfg_desc =
+        {
+            .config =
+                {
+                    .bLength = sizeof(struct usb_config_descriptor),
+                    .bDescriptorType = USB_DTYPE_CONFIGURATION,
+                    .wTotalLength = sizeof(struct HidConfigDescriptor),
+                    .bNumInterfaces = NUM_OF_INTERFACES,
+                    .bConfigurationValue = 1,
+                    .iConfiguration = NO_DESCRIPTOR,
+                    .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED,
+                    .bMaxPower = USB_CFG_POWER_MA(100),
+                },
+            .iad_0 =
+                {
+                    .hid =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 0,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid2 =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 1,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc2 =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in2 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN + 1,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out2 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT + 1,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid3 =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 2,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc3 =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in3 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN + 2,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out3 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT + 2,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid4 =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 3,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc4 =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in4 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN + 3,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out4 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT + 3,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid5 =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 4,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc5 =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in5 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN + 4,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out5 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT + 4,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid6 =
+                        {
+                            .bLength = sizeof(struct usb_interface_descriptor),
+                            .bDescriptorType = USB_DTYPE_INTERFACE,
+                            .bInterfaceNumber = 5,
+                            .bAlternateSetting = 0,
+                            .bNumEndpoints = 2,
+                            .bInterfaceClass = USB_CLASS_HID,
+                            .bInterfaceSubClass = USB_HID_SUBCLASS_NONBOOT,
+                            .bInterfaceProtocol = USB_HID_PROTO_NONBOOT,
+                            .iInterface = NO_DESCRIPTOR,
+                        },
+                    .hid_desc6 =
+                        {
+                            .bLength = sizeof(struct usb_hid_descriptor),
+                            .bDescriptorType = USB_DTYPE_HID,
+                            .bcdHID = VERSION_BCD(1, 0, 0),
+                            .bCountryCode = USB_HID_COUNTRY_NONE,
+                            .bNumDescriptors = 1,
+                            .bDescriptorType0 = USB_DTYPE_HID_REPORT,
+                            .wDescriptorLength0 = sizeof(hid_u2f_report_desc),
+                        },
+                    .hid_ep_in6 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_IN + 5,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                    .hid_ep_out6 =
+                        {
+                            .bLength = sizeof(struct usb_endpoint_descriptor),
+                            .bDescriptorType = USB_DTYPE_ENDPOINT,
+                            .bEndpointAddress = HID_EP_OUT + 5,
+                            .bmAttributes = endpoint_type,
+                            .wMaxPacketSize = HID_U2F_PACKET_LEN,
+                            .bInterval = 1,
+                        },
+                },
+};
+
+static void hid_u2f_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx);
+static void hid_u2f_deinit(usbd_device* dev);
+static void hid_u2f_on_wakeup(usbd_device* dev);
+static void hid_u2f_on_suspend(usbd_device* dev);
+
+//static bool hid_u2f_send_report(uint8_t report_id);
+static usbd_respond hid_u2f_ep_config(usbd_device* dev, uint8_t cfg);
+usbd_respond hid_u2f_controlf(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
+static usbd_device* usb_dev;
+static bool hid_u2f_connected = false;
+
+static void* cb_ctx;
+
+bool furi_hal_hid_u2f_is_connected() {
+    return hid_u2f_connected;
+}
+
+static void callbackf(HidU2fEvent ev, void* context) {
+    FURI_LOG_D(TAG_IF, "callback %d", ev);
+    if(context) {
+        return;
+    }
+}
+
+FuriHalUsbInterface usb_hid_bulk = {
+    .init = hid_u2f_init,
+    .deinit = hid_u2f_deinit,
+    .wakeup = hid_u2f_on_wakeup,
+    .suspend = hid_u2f_on_suspend,
+
+    .dev_descr = (struct usb_device_descriptor*)&hid_u2f_device_desc,
+
+    .str_manuf_descr = (void*)&dev_manuf_desc,
+    .str_prod_descr = (void*)&dev_prod_desc,
+    .str_serial_descr = NULL,
+
+    .cfg_descr = (void*)&hid_u2f_cfg_desc,
+};
+
+FuriHalUsbInterface* getUsbHidBulk() {
+    return &usb_hid_bulk;
+}
+
+typedef struct ThreadContext {
+    uint8_t id;
+    FuriThread* thread;
+    FuriSemaphore* hid;
+} ThreadContext;
+
+static ThreadContext* threadContexts[NUM_OF_INTERFACES];
+static FuriMessageQueue* dataQueue;
+
+static uint32_t tick_space = 0;
+
+static void logQueueStats() {
+    uint32_t ticks = furi_get_tick();
+    if(ticks - tick_space > 4000) {
+        tick_space = ticks;
+        size_t queueSize = furi_message_queue_get_count(dataQueue);
+        size_t freeS = furi_message_queue_get_space(dataQueue);
+        double heapFree = memmgr_get_free_heap() / 1000;
+        double totalHeap = memmgr_get_total_heap() / 1000;
+        FURI_LOG_D(
+            TAG,
+            "Stats:\r\n\tQueueCount: %d\r\n\tQueueSpace: %d\r\n\tHeapFree: %.3f\r\n\tTotalHeap: %.3f",
+            queueSize,
+            freeS,
+            heapFree,
+            totalHeap);
+    }
+}
+
+int32_t send_data_thread(void* context) {
+    ThreadContext* ctx = (ThreadContext*)context;
+    while(true) {
+        ThreadMessage msg;
+        furi_check(furi_message_queue_get(dataQueue, &msg, FuriWaitForever) == FuriStatusOk);
+        if(msg.dataPointer == NULL) {
+            FURI_LOG_D(
+                TAG_IF,
+                "Shutting down thread %s",
+                furi_thread_get_name(furi_thread_get_id(ctx->thread)));
+            return 0;
+        }
+        furi_check(furi_semaphore_acquire(ctx->hid, FuriWaitForever) == FuriStatusOk);
+
+        usbd_ep_write(usb_dev, HID_EP_IN + ctx->id, msg.dataPointer, 64);
+        free(msg.dataPointer);
+        furi_thread_yield();
+    }
+}
+
+int32_t receive_data_thread(void* context) {
+    ThreadContext* ctx = (ThreadContext*)context;
+    uint8_t* data;
+    int32_t len;
+    bool received = false;
+    while(true) {
+        if(!received) {
+            furi_check(furi_semaphore_acquire(ctx->hid, FuriWaitForever) == FuriStatusOk);
+        } else {
+            FuriStatus status = furi_semaphore_acquire(ctx->hid, 5000);
+            if(status == FuriStatusErrorTimeout) {
+                FURI_LOG_I(TAG_IF, "Timeout, terminating thread %d", ctx->id);
+                ThreadMessage msg = {.dataPointer = NULL};
+                furi_check(
+                    furi_message_queue_put(dataQueue, &msg, FuriWaitForever) == FuriStatusOk);
+                return 0;
+            }
+            furi_check(status == FuriStatusOk);
+        }
+
+        data = malloc(64);
+        len = usbd_ep_read(usb_dev, HID_EP_OUT + ctx->id, data, 64);
+        if(len <= 0) {
+            FURI_LOG_D(TAG_IF, "Received 0 bytes (EP %d)", ctx->id);
+            free(data);
+            furi_thread_yield();
+            continue;
+        } else if(len < 64) {
+            FURI_LOG_D(TAG_IF, "Received %ld bytes (EP %d)", len, ctx->id);
+        }
+
+        //FURI_LOG_D(TAG_IF, "Received %ld bytes (EP %d)", len, ctx->id);
+        //FURI_LOG_D(TAG_IF, "0: %02x last: %02x", data[0], data[len-1]);
+        received = true;
+        ThreadMessage msg = {.dataPointer = data};
+        furi_check(furi_message_queue_put(dataQueue, &msg, FuriWaitForever) == FuriStatusOk);
+        logQueueStats();
+        furi_thread_yield();
+    }
+}
+
+static int sendThreads = 0;
+
+void initializeSendingData(int sendingThreads) {
+    furi_check(sendingThreads <= NUM_OF_INTERFACES);
+    sendThreads = sendingThreads;
+    for(int i = 0; i < sendThreads; i++) {
+        ThreadContext* ctx = threadContexts[i];
+
+        char name[15];
+        snprintf(name, 15, "SendThread %d", ctx->id);
+        FURI_LOG_D(TAG_IF, "Spawn thread %s", name);
+        ctx->thread = furi_thread_alloc_ex(name, 512, send_data_thread, ctx);
+        furi_thread_start(ctx->thread);
+    }
+}
+
+void stopSendingData() {
+    for(int i = 0; i < sendThreads; i++) {
+        ThreadMessage msg = {.dataPointer = NULL};
+        furi_check(furi_message_queue_put(dataQueue, &msg, FuriWaitForever) == FuriStatusOk);
+    }
+
+    for(int i = 0; i < sendThreads; i++) {
+        ThreadContext* ctx = threadContexts[i];
+        furi_thread_join(ctx->thread);
+        furi_thread_free(ctx->thread);
+    }
+}
+
+FuriMessageQueue* initializeReceivingData() {
+    for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+        ThreadContext* ctx = threadContexts[i];
+
+        char name[16];
+        snprintf(name, 16, "RecThread %d", ctx->id);
+        FURI_LOG_D(TAG_IF, "Spawn thread %s", name);
+        ctx->thread = furi_thread_alloc_ex(name, 1024, receive_data_thread, ctx);
+        furi_thread_start(ctx->thread);
+    }
+
+    return dataQueue;
+}
+
+void stopReceivingData() {
+    for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+        ThreadContext* ctx = threadContexts[i];
+        furi_thread_join(ctx->thread);
+        furi_thread_free(ctx->thread);
+    }
+}
+
+void sendBulkData(uint8_t* data, uint8_t len) {
+    UNUSED(len);
+    ThreadMessage msg = {.dataPointer = data};
+    logQueueStats();
+    furi_check(furi_message_queue_put(dataQueue, &msg, FuriWaitForever) == FuriStatusOk);
+}
+
+void sendViaEP(uint8_t* data, int interfaceNumber) {
+    ThreadContext* ctx = threadContexts[interfaceNumber];
+    furi_check(furi_semaphore_acquire(ctx->hid, FuriWaitForever) == FuriStatusOk);
+    usbd_ep_write(usb_dev, HID_EP_IN + ctx->id, data, 64);
+}
+
+void receiveFromEP(uint8_t* outBuf, int interfaceNumber) {
+    ThreadContext* ctx = threadContexts[interfaceNumber];
+    furi_check(furi_semaphore_acquire(ctx->hid, FuriWaitForever) == FuriStatusOk);
+    usbd_ep_read(usb_dev, HID_EP_OUT + ctx->id, outBuf, 64);
+}
+
+static void hid_u2f_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
+    FURI_LOG_D(TAG_IF, "hid_u2f_init");
+    UNUSED(intf);
+    UNUSED(ctx);
+
+    if(threadContexts[0] == NULL) {
+        dataQueue = furi_message_queue_alloc(1000, sizeof(ThreadMessage));
+
+        for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+            ThreadContext* ctx = malloc(sizeof(ThreadContext));
+            threadContexts[i] = ctx;
+
+            ctx->hid = furi_semaphore_alloc(1, 1);
+            ctx->id = i;
+        }
+    }
+
+    usb_dev = dev;
+
+    usbd_reg_config(dev, hid_u2f_ep_config);
+    usbd_reg_control(dev, hid_u2f_controlf);
+
+    usbd_connect(dev, true);
+}
+
+static void hid_u2f_deinit(usbd_device* dev) {
+    FURI_LOG_D(TAG_IF, "hid_u2f_deinit");
+    usbd_reg_config(dev, NULL);
+    usbd_reg_control(dev, NULL);
+
+    for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+        ThreadContext* ctx = threadContexts[i];
+        furi_semaphore_free(ctx->hid);
+
+        free(threadContexts[i]);
+    }
+}
+
+static void hid_u2f_on_wakeup(usbd_device* dev) {
+    FURI_LOG_D(TAG_IF, "hid_u2f_on_wakeup");
+    UNUSED(dev);
+    hid_u2f_connected = true;
+    callbackf(HidU2fConnected, cb_ctx);
+}
+
+static void hid_u2f_on_suspend(usbd_device* dev) {
+    FURI_LOG_D(TAG_IF, "hid_u2f_on_suspend");
+    UNUSED(dev);
+    if(hid_u2f_connected) {
+        hid_u2f_connected = false;
+        for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+            furi_semaphore_release(threadContexts[i]->hid);
+        }
+        callbackf(HidU2fDisconnected, cb_ctx);
+    }
+}
+
+static void hid_u2f_txrx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
+    UNUSED(dev);
+    UNUSED(event);
+    UNUSED(ep);
+
+    if(ep >= HID_EP_IN) {
+        furi_semaphore_release(threadContexts[ep - HID_EP_IN]->hid);
+    } else {
+        furi_semaphore_release(threadContexts[ep - HID_EP_OUT]->hid);
+    }
+}
+
+/* Configure endpoints */
+static usbd_respond hid_u2f_ep_config(usbd_device* dev, uint8_t cfg) {
+    switch(cfg) {
+    case 0:
+        /* deconfiguring device */
+        for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+            usbd_ep_deconfig(dev, HID_EP_OUT + i);
+            usbd_ep_deconfig(dev, HID_EP_IN + i);
+            usbd_reg_endpoint(dev, HID_EP_OUT + i, 0);
+            usbd_reg_endpoint(dev, HID_EP_IN + i, 0);
+        }
+
+        return usbd_ack;
+    case 1:
+        /* configuring device */
+
+        for(int i = 0; i < NUM_OF_INTERFACES; i++) {
+            usbd_ep_config(dev, HID_EP_IN + i, endpoint_type, HID_U2F_PACKET_LEN);
+            usbd_ep_config(dev, HID_EP_OUT + i, endpoint_type, HID_U2F_PACKET_LEN);
+            usbd_reg_endpoint(dev, HID_EP_IN + i, hid_u2f_txrx_ep_callback);
+            usbd_reg_endpoint(dev, HID_EP_OUT + i, hid_u2f_txrx_ep_callback);
+            usbd_ep_write(dev, HID_EP_IN + 1, 0, 0);
+        }
+        return usbd_ack;
+    default:
+        return usbd_fail;
+    }
+}
+
+/* Control requests handler */
+usbd_respond hid_u2f_controlf(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
+    UNUSED(callback);
+
+    FURI_LOG_D(
+        TAG_IF,
+        "control: RT %02x, R %02x, V %04x, I %04x, L %04x",
+        req->bmRequestType,
+        req->bRequest,
+        req->wValue,
+        req->wIndex,
+        req->wLength);
+
+    /* HID control requests */
+    if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
+           (USB_REQ_INTERFACE | USB_REQ_CLASS) &&
+       req->wIndex == 0) {
+        switch(req->bRequest) {
+        case USB_HID_SETIDLE:
+
+            return usbd_ack;
+        case USB_STD_SET_INTERFACE:
+            return usbd_ack;
+        default:
+            return usbd_fail;
+        }
+    }
+    if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
+           (USB_REQ_INTERFACE | USB_REQ_STANDARD) &&
+       req->bRequest == USB_STD_GET_DESCRIPTOR) {
+        switch(req->wValue >> 8) {
+        case USB_DTYPE_HID:
+            //dev->status.data_ptr = (uint8_t*)&(hid_u2f_cfg_desc.iad_0.hid_desc2);
+            //dev->status.data_count = sizeof(hid_u2f_cfg_desc.iad_0.hid_desc2);
+            return usbd_fail;
+        case USB_DTYPE_HID_REPORT:
+            dev->status.data_ptr = (uint8_t*)hid_u2f_report_desc;
+            dev->status.data_count = sizeof(hid_u2f_report_desc);
+            return usbd_ack;
+        case USB_STD_SET_INTERFACE:
+        case USB_STD_GET_INTERFACE:
+            return usbd_ack;
+        default:
+            return usbd_fail;
+        }
+    }
+    return usbd_fail;
+}

+ 20 - 0
non_catalog_apps/hid_file_transfer/usbif.h

@@ -0,0 +1,20 @@
+#include "furi_hal_usb.h"
+
+#define NUM_OF_INTERFACES 6
+
+typedef struct ThreadMessage {
+    void* dataPointer;
+} ThreadMessage;
+
+void sendBulkData(uint8_t* data, uint8_t len);
+
+void initializeSendingData(int numberOfInterfaces);
+void stopSendingData();
+
+FuriMessageQueue* initializeReceivingData();
+void stopReceivingData();
+
+FuriHalUsbInterface *getUsbHidBulk();
+
+void sendViaEP(uint8_t* data, int interfaceNumber);
+void receiveFromEP(uint8_t* outBuf, int interfaceNumber);

Некоторые файлы не были показаны из-за большого количества измененных файлов