From 7a3e7b8b44a58338100c667345773b98ef28ce3f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Jan 2024 21:54:30 +0300 Subject: [PATCH] add new app, apply fixes --- .../hid_file_transfer/.vscode/settings.json | 16 + .../hid_file_transfer/application.fam | 17 + .../hid_file_transfer/assets/arrows.pixil | 1 + .../hid_file_transfer/assets/left_14.png | Bin 0 -> 323 bytes .../hid_file_transfer/assets/right_14.png | Bin 0 -> 322 bytes .../hid_file_transfer/constants.h | 4 + .../hid_file_transfer/filelogger.c | 108 +++ .../hid_file_transfer/filelogger.h | 70 ++ .../hid_file_transfer/hidtransfer_icons.h | 6 + .../hid_file_transfer/icons/hid_10px.png | Bin 0 -> 320 bytes non_catalog_apps/hid_file_transfer/main.c | 470 +++++++++++ non_catalog_apps/hid_file_transfer/usbif.c | 741 ++++++++++++++++++ non_catalog_apps/hid_file_transfer/usbif.h | 20 + 13 files changed, 1453 insertions(+) create mode 100644 non_catalog_apps/hid_file_transfer/.vscode/settings.json create mode 100644 non_catalog_apps/hid_file_transfer/application.fam create mode 100644 non_catalog_apps/hid_file_transfer/assets/arrows.pixil create mode 100644 non_catalog_apps/hid_file_transfer/assets/left_14.png create mode 100644 non_catalog_apps/hid_file_transfer/assets/right_14.png create mode 100644 non_catalog_apps/hid_file_transfer/constants.h create mode 100644 non_catalog_apps/hid_file_transfer/filelogger.c create mode 100644 non_catalog_apps/hid_file_transfer/filelogger.h create mode 100644 non_catalog_apps/hid_file_transfer/hidtransfer_icons.h create mode 100644 non_catalog_apps/hid_file_transfer/icons/hid_10px.png create mode 100644 non_catalog_apps/hid_file_transfer/main.c create mode 100644 non_catalog_apps/hid_file_transfer/usbif.c create mode 100644 non_catalog_apps/hid_file_transfer/usbif.h diff --git a/non_catalog_apps/hid_file_transfer/.vscode/settings.json b/non_catalog_apps/hid_file_transfer/.vscode/settings.json new file mode 100644 index 00000000000..e280d78e9be --- /dev/null +++ b/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" + } +} \ No newline at end of file diff --git a/non_catalog_apps/hid_file_transfer/application.fam b/non_catalog_apps/hid_file_transfer/application.fam new file mode 100644 index 00000000000..d9bf9b87f8a --- /dev/null +++ b/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"], +) diff --git a/non_catalog_apps/hid_file_transfer/assets/arrows.pixil b/non_catalog_apps/hid_file_transfer/assets/arrows.pixil new file mode 100644 index 00000000000..f49259804b2 --- /dev/null +++ b/non_catalog_apps/hid_file_transfer/assets/arrows.pixil @@ -0,0 +1 @@ +{"application":"pixil","type":".pixil","version":"2.7.0","website":"pixilart.com","author":"https://www.pixilart.com","contact":"support@pixilart.com","width":14,"height":14,"colors":{"default":["000000","ffffff","f44336","E91E63","9C27B0","673AB7","3F51B5","2196F3","03A9F4","00BCD4","009688","4CAF50","8BC34A","CDDC39","FFEB3B","FFC107","FF9800","FF5722","795548","9E9E9E","607D8B","ffebee","ffcdd2","ef9a9a","e57373","ef5350","e53935","d32f2f","c62828","b71c1c","ff8a80","ff5252","ff1744","d50000","fce4ec","f8bbd0","f48fb1","f06292","ec407a","e91e63","d81b60","c2185b","ad1457","880e4f","ff80ab","ff4081","f50057","c51162","f3e5f5","e1bee7","ce93d8","ba68c8","ab47bc","9c27b0","8e24aa","7b1fa2","6a1b9a","4a148c","ea80fc","e040fb","d500f9","aa00ff","ede7f6","d1c4e9","b39ddb","9575cd","7e57c2","673ab7","5e35b1","512da8","4527a0","311b92","b388ff","7c4dff","651fff","6200ea","e8eaf6","c5cae9","9fa8da","7986cb","5c6bc0","3f51b5","3949ab","303f9f","283593","1a237e","8c9eff","536dfe","3d5afe","304ffe","e3f2fd","bbdefb","90caf9","64b5f6","42a5f5","2196f3","1e88e5","1976d2","1565c0","0d47a1","82b1ff","448aff","2979ff","2962ff","e1f5fe","b3e5fc","81d4fa","4fc3f7","29b6f6","03a9f4","039be5","0288d1","0277bd","01579b","80d8ff","40c4ff","00b0ff","0091ea","e0f7fa","b2ebf2","80deea","4dd0e1","26c6da","00bcd4","00acc1","0097a7","00838f","006064","84ffff","18ffff","00e5ff","00b8d4","e0f2f1","b2dfdb","80cbc4","4db6ac","26a69a","009688","00897b","00796b","00695c","004d40","a7ffeb","64ffda","1de9b6","00bfa5","e8f5e9","c8e6c9","a5d6a7","81c784","66bb6a","4caf50","43a047","388e3c","2e7d32","1b5e20","b9f6ca","69f0ae","00e676","00c853","f1f8e9","dcedc8","c5e1a5","aed581","9ccc65","8bc34a","7cb342","689f38","558b2f","33691e","ccff90","b2ff59","76ff03","64dd17","f9fbe7","f0f4c3","e6ee9c","dce775","d4e157","cddc39","c0ca33","afb42b","9e9d24","827717","f4ff81","eeff41","c6ff00","aeea00","fffde7","fff9c4","fff59d","fff176","ffee58","ffeb3b","fdd835","fbc02d","f9a825","f57f17","ffff8d","ffff00","ffea00","ffd600","fff8e1","ffecb3","ffe082","ffd54f","ffca28","ffc107","ffb300","ffa000","ff8f00","ff6f00","ffe57f","ffd740","ffc400","ffab00","fff3e0","ffe0b2","ffcc80","ffb74d","ffa726","ff9800","fb8c00","f57c00","ef6c00","e65100","ffd180","ffab40","ff9100","ff6d00","fbe9e7","ffccbc","ffab91","ff8a65","ff7043","ff5722","f4511e","e64a19","d84315","bf360c","ff9e80","ff6e40","ff3d00","dd2c00","efebe9","d7ccc8","bcaaa4","a1887f","8d6e63","795548","6d4c41","5d4037","4e342e","3e2723","fafafa","f5f5f5","eeeeee","e0e0e0","bdbdbd","9e9e9e","757575","616161","424242","212121","eceff1","cfd8dc","b0bec5","90a4ae","78909c","607d8b","546e7a","455a64","37474f","263238"],"simple":["ffffff","d4d4d4","a1a1a1","787878","545454","303030","000000","edc5c5","e68383","ff0000","de2424","ad3636","823737","592b2b","f5d2ee","eb8dd7","f700b9","bf1f97","9c277f","732761","4f2445","e2bcf7","bf79e8","9d00ff","8330ba","6d3096","502c69","351b47","c5c3f0","736feb","0905f7","2e2eb0","2d2d80","252554","090936","c7e2ed","6ac3e6","00bbff","279ac4","347c96","2d5b6b","103947","bbf0d9","6febb3","00ff88","2eb878","349166","2b694c","0c3d25","c2edc0","76ed70","0dff00","36c72c","408c3b","315c2e","144511","d6edbb","b5eb73","8cff00","89c93a","6f8f44","4b632a","2a400c","f1f2bf","eef069","ffff00","baba30","91913f","5e5e2b","3b3b09","ffdeb8","f2ae61","ff8400","c48037","85623d","573e25","3d2309","fcbbae","ff8066","ff2b00","cc553d","9c5b4e","61372e","36130b"],"common":["000000","ffffff","464646","b4b4b4","990030","9c5a3c","ed1c24","ffa3b1","ff7e00","e5aa7a","ffc20e","f5e49c","fff200","fff9bd","a8e61d","d3f9bc","22b14c","00b7ef","99d9ea","4d6df3","709ad1","2f3699","546d8e","6f3198","b5a5d5"],"skin tones":["ffe0bd","ffdbac","ffcd94","eac086","e0ac69","f1c27d","ffad60","c68642","8d5524","896347","765339","613D24","4C2D17","391E0B","351606","2D1304","180A01","090300"],"NightClub":["000000","041020","061428","0A264D","0C3064","0E3A77","134FA3","134997","1C74EE","2BAFFF","38C5F8","12DCFE","31FFFF","F7FEFE"]},"colorSelected":"NightClub","frames":[{"name":"","speed":100,"layers":[{"id":0,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAHElEQVQoU2P8DwQMZADGUY24Q200cPCkqCEUOAC66zfXeZ5jnwAAAABJRU5ErkJggg==","edit":false,"name":"Background","opacity":1,"active":true,"unqid":"lx72","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":1,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAS0lEQVQoU2NkIBMwkqmPgRiN/4GGY6gjViPIYShqSdGIohlZI8hJxACwHnI0gvWRoxHDRlzORPYC3CJSAoesUCUrHrF6gRinUlcjACa0CQ8exkIDAAAAAElFTkSuQmCC","edit":false,"name":"Right","opacity":1,"active":true,"unqid":"knihbc","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}},{"id":2,"src":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAATklEQVQoU2NkIBMwkqmPAZ/G/0BDccrjkgBpAgGSNMI0kaQRWRM+7zMiO4VYTWCXoPuBWM0YGkGmkeVHmL/IClVkzSRFB1GJiSZJDq/NAGa0CQ+hIbooAAAAAElFTkSuQmCC","edit":false,"name":"Left","opacity":1,"active":false,"unqid":"knihbc","options":{"blend":"source-over","locked":false,"filter":{"brightness":"100%","contrast":"100%","grayscale":"0%","blur":0,"dropshadow_x":0,"dropshadow_y":0,"dropshadow_blur":0,"dropshadow_alpha":1}}}],"active":true,"selectedLayer":"0","unqid":"96z1ir","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAATklEQVQoU2P8DwQMZABGmmlkZGRkwOYogjaCNIIAumaiNaJrhmuEmUwonGA2k6wRZjPJGjFsxOVEZC8gBxDRgUNWqJIVjzi9QLMkh8tGABWDUte2etveAAAAAElFTkSuQmCC","width":14,"height":14}],"currentFrame":0,"speed":100,"name":"Untitled","preview":"data:image/pngp98kjasdnasd983/24kasdjasdbase64,iVBORw0KGgoAAAANSUhEUgAAAA4A/sfR5H8Fkddasdmnacvx/AAAOCAYAAAAfSC3RAAAATklEQVQoU2P8DwQMZABGmmlkZGRkwOYogjaCNIIAumaiNaJrhmuEmUwonGA2k6wRZjPJGjFsxOVEZC8gBxDRgUNWqJIVjzi9QLMkh8tGABWDUte2etveAAAAAElFTkSuQmCC","previewApp":"","art_edit_id":0,"palette_id":"590","created_at":1688825172138,"updated_at":1688825172138,"persLayers":true,"id":1688824077663} \ No newline at end of file diff --git a/non_catalog_apps/hid_file_transfer/assets/left_14.png b/non_catalog_apps/hid_file_transfer/assets/left_14.png new file mode 100644 index 0000000000000000000000000000000000000000..c4cabe360e291a596a66381dede83052e8473ad1 GIT binary patch literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^d?3sSBpA$%1!{m4Qz zd!d9@l&tm@pbpg%*NBpo#FA92U{GObV4zz1SThl*n8DN4&t;ucLK6Vm Cty&8J literal 0 HcmV?d00001 diff --git a/non_catalog_apps/hid_file_transfer/assets/right_14.png b/non_catalog_apps/hid_file_transfer/assets/right_14.png new file mode 100644 index 0000000000000000000000000000000000000000..546979419bcbf8c2e4d7fbd5ca7c5f3c4482cd42 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^d?3sSBpA$%1!{m4QvU|?lnpufiQ zBZ`LH{FKbJN^}h-R)z);4NsGOKpxP5+fb63n_66wm|K9Z$IQyu45DXxS^sLF9z~Fz zjLhPa{Pd#4T&v{#ypp2C;u78BlA_d-zopr09vYA A0RR91 literal 0 HcmV?d00001 diff --git a/non_catalog_apps/hid_file_transfer/constants.h b/non_catalog_apps/hid_file_transfer/constants.h new file mode 100644 index 00000000000..26243d41910 --- /dev/null +++ b/non_catalog_apps/hid_file_transfer/constants.h @@ -0,0 +1,4 @@ + + +#define TAG "HIDT_main" +#define TAG_IF "HIDT_IF" diff --git a/non_catalog_apps/hid_file_transfer/filelogger.c b/non_catalog_apps/hid_file_transfer/filelogger.c new file mode 100644 index 00000000000..90a54c042ec --- /dev/null +++ b/non_catalog_apps/hid_file_transfer/filelogger.c @@ -0,0 +1,108 @@ +#include "filelogger.h" +#include +#include +#include +#include + +#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; +} diff --git a/non_catalog_apps/hid_file_transfer/filelogger.h b/non_catalog_apps/hid_file_transfer/filelogger.h new file mode 100644 index 00000000000..16dd8999190 --- /dev/null +++ b/non_catalog_apps/hid_file_transfer/filelogger.h @@ -0,0 +1,70 @@ +/** + * @file log.h + * Furi Logging system + */ +#pragma once + +#include + +#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 diff --git a/non_catalog_apps/hid_file_transfer/hidtransfer_icons.h b/non_catalog_apps/hid_file_transfer/hidtransfer_icons.h new file mode 100644 index 00000000000..a562c1512b2 --- /dev/null +++ b/non_catalog_apps/hid_file_transfer/hidtransfer_icons.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +extern const Icon I_left_14; +extern const Icon I_right_14; diff --git a/non_catalog_apps/hid_file_transfer/icons/hid_10px.png b/non_catalog_apps/hid_file_transfer/icons/hid_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..712c6c53ecb2a7b7ba9057004f3c6eae69e64bb0 GIT binary patch literal 320 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsF(rAsyD%``?Gj!B43Il{a4C~Cf z?JA +#include + +#include "usbif.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "filelogger.h" +#include "constants.h" +#include +#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; +} diff --git a/non_catalog_apps/hid_file_transfer/usbif.c b/non_catalog_apps/hid_file_transfer/usbif.c new file mode 100644 index 00000000000..019b13637e3 --- /dev/null +++ b/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 +#include "usb.h" +#include +#include "usbd_core.h" +#include "usb_std.h" +#include "filelogger.h" + +#include "usbif.h" +#include +#include "constants.h" +#include + +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; +} diff --git a/non_catalog_apps/hid_file_transfer/usbif.h b/non_catalog_apps/hid_file_transfer/usbif.h new file mode 100644 index 00000000000..e7e77a1cebb --- /dev/null +++ b/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);