diff --git a/.github/workflows/webhook.yml b/.github/workflows/webhook.yml index 7409e201e6..003442a1fb 100644 --- a/.github/workflows/webhook.yml +++ b/.github/workflows/webhook.yml @@ -7,8 +7,6 @@ on: - "Build" - "Lint" - "Release" - - "SonarCloud" - - "Submodules" types: - "completed" issues: diff --git a/.gitmodules b/.gitmodules index a52b87b855..f6bb3b18a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -44,3 +44,6 @@ [submodule "documentation/doxygen/doxygen-awesome-css"] path = documentation/doxygen/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git +[submodule "lib/tlsf"] + path = lib/tlsf + url = https://github.com/espressif/tlsf diff --git a/.pvsoptions b/.pvsoptions index 8606eef154..590a34de8b 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 01e2c17f66..399e2d4188 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,8 +1,5 @@ #include "../minunit.h" -#include -#include -#include -#include +#include void test_furi_memmgr(void) { void* ptr; @@ -37,3 +34,260 @@ void test_furi_memmgr(void) { } free(ptr); } + +static void test_memmgr_malloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = malloc(allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "malloc failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after malloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (malloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_realloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = realloc(ptr, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "realloc(NULL) failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after realloc(NULL)"; + break; + } + } + + memset(ptr, 0x55, allocation_size); + + ptr = realloc(ptr, allocation_size * 2); + + // test that we can reallocate memory + if(ptr == NULL) { + error_message = "realloc failed"; + } + + // test that memory content is preserved + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0x55) { + error_message = "memory is not reallocated after realloc"; + break; + } + } + + // test that remaining memory is zero-initialized + size_t non_zero_count = 0; + for(size_t i = allocation_size; i < allocation_size * 2; i++) { + if(ptr[i] != 0) { + non_zero_count += 1; + } + } + + // check that at most of memory is zero-initialized + // we know that allocator not always can restore content size from a pointer + // so we check against small threshold + if(non_zero_count > 4) { + error_message = "seems that memory is not zero-initialized after realloc"; + } + + uint8_t* null_ptr = realloc(ptr, 0); + + // test that we can free memory + if(null_ptr != NULL) { + error_message = "realloc(0) failed"; + } + + // test that memory is zero-initialized after realloc(0) + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after realloc(0)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = aligned_alloc(alignment, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "aligned_alloc failed"; + } + + // test that memory is aligned + if(((uintptr_t)ptr % alignment) != 0) { + error_message = "memory is not aligned after aligned_alloc"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after aligned_alloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +void test_furi_memmgr_advanced(void) { + const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; + const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); + const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; + const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); + + // do test without memory fragmentation + { + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + } + + // do test with memory fragmentation + { + void* blocks[sizes_count]; + void* guards[sizes_count - 1]; + + // setup guards + for(size_t i = 0; i < sizes_count; i++) { + blocks[i] = malloc(sizes[i]); + if(i < sizes_count - 1) { + guards[i] = malloc(sizes[i]); + } + } + + for(size_t i = 0; i < sizes_count; i++) { + free(blocks[i]); + } + + // do test + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + + // cleanup guards + for(size_t i = 0; i < sizes_count - 1; i++) { + free(guards[i]); + } + } +} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e287f9927f..e0b5916d55 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,6 +9,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); +void test_furi_memmgr_advanced(void); static int foo = 0; @@ -37,6 +38,7 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); + test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4b6503b728..c6304d53ca 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -22,6 +25,23 @@ #define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") #define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") +#define NFC_TEST_FLAG_WORKER_DONE (1) + +typedef enum { + NfcTestMfClassicSendFrameTestStateAuth, + NfcTestMfClassicSendFrameTestStateReadBlock, + + NfcTestMfClassicSendFrameTestStateFail, + NfcTestMfClassicSendFrameTestStateSuccess, +} NfcTestMfClassicSendFrameTestState; + +typedef struct { + NfcTestMfClassicSendFrameTestState state; + BitBuffer* tx_buf; + BitBuffer* rx_buf; + FuriThreadId thread_id; +} NfcTestMfClassicSendFrameTest; + typedef struct { Storage* storage; } NfcTest; @@ -435,6 +455,109 @@ static void mf_classic_value_block(void) { nfc_free(poller); } +NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + MfClassicPoller* instance = event.poller; + NfcTestMfClassicSendFrameTest* frame_test = context; + Iso14443_3aPollerEvent* iso3_event = event.parent_event_data; + + MfClassicError error = MfClassicErrorNone; + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) { + MfClassicKey key = { + .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateReadBlock : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) { + do { + const uint8_t read_block_cmd[] = { + 0x30, + 0x01, + 0x8b, + 0xb9, + }; + bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd)); + + error = mf_classic_poller_send_encrypted_frame( + instance, frame_test->tx_buf, frame_test->rx_buf, 200000); + if(error != MfClassicErrorNone) break; + if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) { + error = MfClassicErrorProtocol; + break; + } + + const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf); + const uint8_t rx_data_ref[16] = {0}; + if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) { + error = MfClassicErrorProtocol; + break; + } + } while(false); + + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateSuccess : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) { + command = NfcCommandStop; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) { + command = NfcCommandStop; + } + } else { + frame_test->state = NfcTestMfClassicSendFrameTestStateFail; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE); + } + + return command; +} + +MU_TEST(mf_classic_send_frame_test) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic); + NfcTestMfClassicSendFrameTest context = { + .state = NfcTestMfClassicSendFrameTestStateAuth, + .thread_id = furi_thread_get_current_id(), + .tx_buf = bit_buffer_alloc(32), + .rx_buf = bit_buffer_alloc(32), + }; + nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag"); + nfc_poller_stop(mfc_poller); + nfc_poller_free(mfc_poller); + + mu_assert( + context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end"); + + bit_buffer_free(context.tx_buf); + bit_buffer_free(context.rx_buf); + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + MU_TEST(mf_classic_dict_test) { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { @@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_1k_7b_file_test); MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(mf_classic_reader); + MU_RUN_TEST(mf_classic_reader); MU_RUN_TEST(mf_classic_write); MU_RUN_TEST(mf_classic_value_block); - + MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); nfc_test_free(); diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 19dcf04eb2..420c9a097b 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -238,10 +238,7 @@ bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf) { &furi_hal_spi_bus_handle_external_extra); // this is needed if multiple SPI devices are connected to the same bus but with different CS pins - if(momentum_settings.spi_cc1101_handle == SpiDefault && !furi_hal_subghz_get_ext_power_amp()) { - furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); - furi_hal_gpio_write(&gpio_ext_pc3, true); - } else if(momentum_settings.spi_cc1101_handle == SpiExtra) { + if(momentum_settings.spi_cc1101_handle == SpiExtra) { furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); furi_hal_gpio_write(&gpio_ext_pa4, true); } @@ -468,6 +465,7 @@ void subghz_device_cc1101_ext_rx(void) { // Go GDO2 (!TX/RX) to high (RX state) cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); if(subghz_device_cc1101_ext->power_amp) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07M20S_AMP_GPIO, 0); diff --git a/applications/examples/example_adc/application.fam b/applications/examples/example_adc/application.fam new file mode 100644 index 0000000000..68aa2ac4d4 --- /dev/null +++ b/applications/examples/example_adc/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_adc", + name="Example: ADC", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_adc_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_adc/example_adc.c b/applications/examples/example_adc/example_adc.c new file mode 100644 index 0000000000..53f8d5c12d --- /dev/null +++ b/applications/examples/example_adc/example_adc.c @@ -0,0 +1,176 @@ +/** + * @file example_adc.c + * @brief ADC example. + */ +#include +#include + +#include +#include +#include + +const uint8_t font[] = + "`\2\3\2\3\4\1\2\4\5\11\0\376\6\376\7\377\1M\2\263\3\370 \6\315\364\371\6!\12\315" + "\364\201\260\35\312Q\0\42\11\315tJI\316\13\0#\14\315\264\223dP*\203R'\1$\15\315\264" + "\262A\311\266D\251l\71\0%\15\315\264\7%\61)J\42\345 \0&\14\315\264\263$\13\223\266$" + "\7\1'\10\315\364\201\60\347\10(\10\315\364\32[\313\0)\11\315\64\322b[\35\2*\12\315\264\263" + "(\222j\71\15+\11\315\364I\331\226\23\1,\10\315\364\271\205Y\10-\10\315\364\31t\26\0.\10" + "\315\364\71\346(\0/\14\315\364\221\60\13\263\60\13C\0\60\13\315\264\245Jb)E:\12\61\12\315" + "\364\201Ll\333A\0\62\12\315\264\245bV\33r\20\63\13\315\264\245Z\232D\221\216\2\64\14\315\364" + "\201LJ\242!\313v\20\65\14\315t\207$\134\223(\322Q\0\66\13\315\264\245p\252D\221\216\2\67" + "\12\315t\207\60+\326a\0\70\13\315\264\245\222T\211\42\35\5\71\13\315\264\245J\24\215\221\216\2:" + "\11\315\364i\71!G\1;\12\315\364I\71!\314B\0<\11\315\364\341\254Z\7\1=\12\315\364)" + "C<\344$\0>\11\315\364\301\264V\207\1\77\12\315\264\245Z\35\312a\0@\14\315\264\245J\242$" + "J\272\203\0A\15\315\264\245J\224\14I\224D\71\10B\13\315t\247\312T\211\222\35\5C\12\315\264" + "\245JX\212t\24D\15\315t\247J\224DI\224\354(\0E\14\315t\207$\234\302p\310A\0F" + "\12\315t\207$\234\302:\1G\14\315\264\245J\230(Q\244\243\0H\17\315t\243$J\206$J\242" + "$\312A\0I\11\315\264\267\260m\7\1J\12\315\364\221\260%\212t\24K\14\315t\243\244\244iI" + "T\7\1L\11\315t\303\216C\16\2M\17\315t\243dH\206$J\242$\312A\0N\16\315t\243" + "D\251(Q\22%Q\16\2O\15\315\264\245J\224DI\24\351(\0P\12\315t\247J\224LaN" + "Q\15\315\264\245J\224DI\42\251\61\0R\14\315t\247J\224L\225(\7\1S\13\315\264\245\222\232" + "D\221\216\2T\10\315\264\267\260;\12U\16\315t\243$J\242$J\242HG\1V\15\315t\243$" + "J\242$Jj\71\14W\17\315t\243$J\242dH\206$\312A\0X\15\315t\243$\212\64\251\22" + "\345 \0Y\13\315t\243$Jja\35\6Z\12\315t\207\60k\34r\20[\10\315\264\264\260G\31" + "\134\12\315\264\303\64L\303\64\14]\10\315t\304\276\351\0^\11\315\364\201,\311\271\1_\7\315\364y" + "\35\4`\10\315t\322\234'\0a\14\315\364IK\224$R\222\203\0b\13\315t\303p\252D\311\216" + "\2c\12\315\364IR%\335A\0d\14\315\364\221\60Z\242$\212v\20e\12\315\364I\322\220\244;" + "\10f\12\315\364\221,\333\302:\12g\14\315\364IK\224D\321\30I\0h\14\315t\303p\252DI" + "\224\203\0i\12\315\364\201\34\21k;\10j\12\315\364\201\34\21\273e\0k\13\315t\303J\244%Q" + "\35\4l\10\315\264\305n;\10m\14\315\364)CRQ\22\245\216\1n\13\315\364)%\245\224D\71" + "\10o\12\315\364IR%\212t\24p\13\315\364)S%J\246\60\4q\13\315\364IK\224D\321X" + "\1r\11\315\364)%\245\230\23s\12\315\364I\313\232\354(\0t\13\315\364\201\60\333\302\64\7\1u" + "\15\315\364)Q\22%\211\224\344 \0v\13\315\364)Q\22%\265\34\6w\13\315\364)\25%Q\272" + "\203\0x\12\315\364)Q\244Iu\20y\15\315\364)Q\22%Q\64F\22\0z\12\315\364)CV" + "\33r\20{\12\315\364\212\265\64\254&\0|\7\315\264\302~\7}\12\315t\322\260\232\205\265\14~\11" + "\315\364II;\13\0\177\6\315\364\371\6\0\0\0\4\377\377\0"; + +#define FONT_HEIGHT (8u) + +typedef float (*ValueConverter)(FuriHalAdcHandle* handle, uint16_t value); + +typedef struct { + const GpioPinRecord* pin; + float value; + ValueConverter converter; + const char* suffix; +} DataItem; + +typedef struct { + size_t count; + DataItem* items; +} Data; + +const GpioPinRecord item_vref = {.name = "VREF", .channel = FuriHalAdcChannelVREFINT}; +const GpioPinRecord item_temp = {.name = "TEMP", .channel = FuriHalAdcChannelTEMPSENSOR}; +const GpioPinRecord item_vbat = {.name = "VBAT", .channel = FuriHalAdcChannelVBAT}; + +static void app_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + Data* data = ctx; + + canvas_set_custom_u8g2_font(canvas, font); + char buffer[64]; + int32_t x = 0, y = FONT_HEIGHT; + for(size_t i = 0; i < data->count; i++) { + if(i == canvas_height(canvas) / FONT_HEIGHT) { + x = 64; + y = FONT_HEIGHT; + } + + snprintf( + buffer, + sizeof(buffer), + "%4s: %4.0f%s\n", + data->items[i].pin->name, + (double)data->items[i].value, + data->items[i].suffix); + canvas_draw_str(canvas, x, y, buffer); + y += FONT_HEIGHT; + } +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t example_adc_main(void* p) { + UNUSED(p); + + // Data + Data data = {}; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + data.count++; + } + } + data.count += 3; // Special channels + data.items = malloc(data.count * sizeof(DataItem)); + size_t item_pos = 0; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + furi_hal_gpio_init(gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + data.items[item_pos].pin = &gpio_pins[i]; + data.items[item_pos].converter = furi_hal_adc_convert_to_voltage; + data.items[item_pos].suffix = "mV"; + item_pos++; + } + } + data.items[item_pos].pin = &item_vref; + data.items[item_pos].converter = furi_hal_adc_convert_vref; + data.items[item_pos].suffix = "mV"; + item_pos++; + data.items[item_pos].pin = &item_temp; + data.items[item_pos].converter = furi_hal_adc_convert_temp; + data.items[item_pos].suffix = "C"; + item_pos++; + data.items[item_pos].pin = &item_vbat; + data.items[item_pos].converter = furi_hal_adc_convert_vbat; + data.items[item_pos].suffix = "mV"; + item_pos++; + furi_assert(item_pos == data.count); + + // Alloc message queue + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, app_draw_callback, &data); + view_port_input_callback_set(view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Initialize ADC + FuriHalAdcHandle* adc_handle = furi_hal_adc_acquire(); + furi_hal_adc_configure(adc_handle); + + // Process events + InputEvent event; + bool running = true; + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if(event.type == InputTypePress && event.key == InputKeyBack) { + running = false; + } + } else { + for(size_t i = 0; i < data.count; i++) { + data.items[i].value = data.items[i].converter( + adc_handle, furi_hal_adc_read(adc_handle, data.items[i].pin->channel)); + } + view_port_update(view_port); + } + } + + furi_hal_adc_release(adc_handle); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + free(data.items); + + return 0; +} diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 405db46e30..a175838e31 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index ba9a354f60..60269a81f7 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -39,7 +39,7 @@ int32_t example_images_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/applications/external b/applications/external index 5890d08342..68b1a9af7c 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 5890d08342cf621f5dba8160b8ee87d53c055988 +Subproject commit 68b1a9af7c8ff1cdb3b9bb269fab014810376f22 diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 6966dc8de5..be5c6a4d0e 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -33,6 +33,7 @@ static const char* known_ext[] = { [ArchiveFileTypeSubghzRemote] = ".txt", [ArchiveFileTypeInfraredRemote] = ".txt", [ArchiveFileTypeBadKb] = ".txt", + [ArchiveFileTypeWAV] = ".wav", [ArchiveFileTypeU2f] = "?", [ArchiveFileTypeApplication] = ".fap", [ArchiveFileTypeJS] = ".js", diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 10b89c3b5c..3406ceea90 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -36,7 +36,13 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder txt_path = archive_get_default_path(ArchiveTabBadKb); break; } - if(txt_path != NULL && furi_string_start_with_str(file->path, txt_path)) { + if(txt_path != NULL) { + size_t len = strlen(txt_path); + if(furi_string_size(file->path) < len) continue; + // Compare but ignore /ext or /any, continue if different (memcmp() != 0) + if(memcmp(furi_string_get_cstr(file->path) + 4, txt_path + 4, len - 4)) { + continue; + } file->type = i; return; } diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 61232f9434..2872105ef7 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -19,6 +19,7 @@ typedef enum { ArchiveFileTypeSubghzRemote, ArchiveFileTypeInfraredRemote, ArchiveFileTypeBadKb, + ArchiveFileTypeWAV, ArchiveFileTypeU2f, ArchiveFileTypeApplication, ArchiveFileTypeJS, diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 3f37990d11..6942144aa4 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -32,6 +32,8 @@ const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { return EXT_PATH("apps/Infrared/ir_remote.fap"); case ArchiveFileTypeBadKb: return "Bad KB"; + case ArchiveFileTypeWAV: + return EXT_PATH("apps/Media/wav_player.fap"); case ArchiveFileTypeU2f: return "U2F"; case ArchiveFileTypeUpdateManifest: diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index c29dec9476..8894496e89 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -34,6 +34,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeSubghzRemote] = &I_subrem_10px, [ArchiveFileTypeInfraredRemote] = &I_ir_scope_10px, [ArchiveFileTypeBadKb] = &I_badkb_10px, + [ArchiveFileTypeWAV] = &I_music_10px, [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeApplication] = &I_Apps_10px, [ArchiveFileTypeJS] = &I_js_script_10px, diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c index 551eb24a28..21e160264d 100644 --- a/applications/main/bad_kb/bad_kb_app.c +++ b/applications/main/bad_kb/bad_kb_app.c @@ -36,6 +36,7 @@ void bad_kb_load_settings(BadKbApp* app) { FlipperFormat* file = flipper_format_file_alloc(storage); if(flipper_format_file_open_existing(file, BAD_KB_SETTINGS_PATH)) { FuriString* tmp_str = furi_string_alloc(); + uint32_t tmp_uint = 0; if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) { furi_string_reset(app->keyboard_layout); @@ -47,11 +48,17 @@ void bad_kb_load_settings(BadKbApp* app) { flipper_format_rewind(file); } - if(!flipper_format_read_bool(file, "Bt_Remember", &app->bt_remember, 1)) { - app->bt_remember = false; + if(!flipper_format_read_bool(file, "Bt_Remember", &cfg->ble.bonding, 1)) { + cfg->ble.bonding = false; flipper_format_rewind(file); } + if(!flipper_format_read_uint32(file, "Bt_Pairing", &tmp_uint, 1)) { + tmp_uint = GapPairingNone; + flipper_format_rewind(file); + } + cfg->ble.pairing = tmp_uint; + if(flipper_format_read_string(file, "Bt_Name", tmp_str)) { strlcpy(cfg->ble.name, furi_string_get_cstr(tmp_str), sizeof(cfg->ble.name)); } else { @@ -115,9 +122,12 @@ static void bad_kb_save_settings(BadKbApp* app) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) { + uint32_t tmp_uint = 0; flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout); flipper_format_write_bool(file, "Is_Bt", &app->is_bt, 1); - flipper_format_write_bool(file, "Bt_Remember", &app->bt_remember, 1); + flipper_format_write_bool(file, "Bt_Remember", &cfg->ble.bonding, 1); + tmp_uint = cfg->ble.pairing; + flipper_format_write_uint32(file, "Bt_Pairing", &tmp_uint, 1); flipper_format_write_string_cstr(file, "Bt_Name", cfg->ble.name); flipper_format_write_hex(file, "Bt_Mac", (uint8_t*)&cfg->ble.mac, sizeof(cfg->ble.mac)); flipper_format_write_string_cstr(file, "Usb_Manuf", cfg->usb.manuf); @@ -143,22 +153,21 @@ void bad_kb_app_show_loading_popup(BadKbApp* app, bool show) { int32_t bad_kb_conn_apply(BadKbApp* app) { if(app->is_bt) { - bt_timeout = bt_hid_delays[LevelRssi39_0]; - bt_disconnect(app->bt); - furi_delay_ms(200); - bt_keys_storage_set_storage_path(app->bt, BAD_KB_KEYS_PATH); - - // Setup new config + // Setup profile config BadKbConfig* cfg = app->set_bt_id ? &app->id_config : &app->config; memcpy(&app->cur_ble_cfg, &cfg->ble, sizeof(cfg->ble)); - app->cur_ble_cfg.bonding = app->bt_remember; - if(app->bt_remember) { - app->cur_ble_cfg.pairing = GapPairingPinCodeVerifyYesNo; - } else { - app->cur_ble_cfg.pairing = GapPairingNone; + if(app->cur_ble_cfg.bonding) { + // Hardcode mac for remember mode + // Change in config copy to preserve user choice for non-remember mode memcpy(app->cur_ble_cfg.mac, BAD_KB_BOUND_MAC, sizeof(BAD_KB_BOUND_MAC)); } + // Prepare for new profile + bt_timeout = bt_hid_delays[LevelRssi39_0]; + bt_disconnect(app->bt); + furi_delay_ms(200); + bt_keys_storage_set_storage_path(app->bt, BAD_KB_KEYS_PATH); + // Set profile app->ble_hid = bt_profile_start(app->bt, ble_profile_hid, &app->cur_ble_cfg); furi_check(app->ble_hid); @@ -246,7 +255,8 @@ void bad_kb_config_refresh(BadKbApp* app) { bad_kb_conn_reset(app); } else { BleProfileHidParams* cur = &app->cur_ble_cfg; - apply = apply || cfg->ble.bonding != app->bt_remember; + apply = apply || cfg->ble.bonding != cur->bonding; + apply = apply || cfg->ble.pairing != cur->pairing; apply = apply || strncmp(cfg->ble.name, cur->name, sizeof(cfg->ble.name)); apply = apply || memcmp(cfg->ble.mac, cur->mac, sizeof(cfg->ble.mac)); } diff --git a/applications/main/bad_kb/bad_kb_app_i.h b/applications/main/bad_kb/bad_kb_app_i.h index e009f0e602..a7d3844ace 100644 --- a/applications/main/bad_kb/bad_kb_app_i.h +++ b/applications/main/bad_kb/bad_kb_app_i.h @@ -72,7 +72,6 @@ struct BadKbApp { Bt* bt; bool is_bt; - bool bt_remember; BadKbConfig config; // User options BadKbConfig id_config; // ID and BT_ID values diff --git a/applications/main/bad_kb/helpers/ducky_script.c b/applications/main/bad_kb/helpers/ducky_script.c index 4e7e48e223..925bce5d45 100644 --- a/applications/main/bad_kb/helpers/ducky_script.c +++ b/applications/main/bad_kb/helpers/ducky_script.c @@ -344,6 +344,10 @@ static bool ducky_set_bt_id(BadKbScript* bad_kb, const char* line) { strlcpy(cfg->ble.name, line + mac_len, sizeof(cfg->ble.name)); FURI_LOG_D(WORKER_TAG, "set bt id: %s", line); + + // Can't set bonding and pairing via BT_ID, sync with user choice instead + cfg->ble.bonding = bad_kb->app->config.ble.bonding; + cfg->ble.pairing = bad_kb->app->config.ble.pairing; return true; } diff --git a/applications/main/bad_kb/scenes/bad_kb_scene_config.c b/applications/main/bad_kb/scenes/bad_kb_scene_config.c index cc7cefdc02..52e12af9cd 100644 --- a/applications/main/bad_kb/scenes/bad_kb_scene_config.c +++ b/applications/main/bad_kb/scenes/bad_kb_scene_config.c @@ -8,6 +8,7 @@ enum VarItemListIndex { enum VarItemListIndexBt { VarItemListIndexBtRemember = VarItemListIndexConnection + 1, + VarItemListIndexBtPairing, VarItemListIndexBtDeviceName, VarItemListIndexBtMacAddress, VarItemListIndexBtRandomizeMac, @@ -29,11 +30,35 @@ void bad_kb_scene_config_connection_callback(VariableItem* item) { void bad_kb_scene_config_bt_remember_callback(VariableItem* item) { BadKbApp* bad_kb = variable_item_get_context(item); - bad_kb->bt_remember = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + bool value = variable_item_get_current_value_index(item); + // Set user config and remember + bad_kb->config.ble.bonding = value; + // Apply to ID config so its temporarily overridden (currently can't set bonding with BT_ID anyway) + if(bad_kb->set_bt_id) { + bad_kb->id_config.ble.bonding = value; + } + variable_item_set_current_value_text(item, value ? "ON" : "OFF"); view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtRemember); } +const char* const bt_pairing_names[GapPairingCount] = { + "YesNo", + "PIN Type", + "PIN Y/N", +}; +void bad_kb_scene_config_bt_pairing_callback(VariableItem* item) { + BadKbApp* bad_kb = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + // Set user config and remember + bad_kb->config.ble.pairing = index; + // Apply to ID config so its temporarily overridden (currently can't set pairing with BT_ID anyway) + if(bad_kb->set_bt_id) { + bad_kb->id_config.ble.pairing = index; + } + variable_item_set_current_value_text(item, bt_pairing_names[index]); + view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexBtPairing); +} + void bad_kb_scene_config_var_item_list_callback(void* context, uint32_t index) { BadKbApp* bad_kb = context; view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index); @@ -52,20 +77,31 @@ void bad_kb_scene_config_on_enter(void* context) { variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB"); if(bad_kb->is_bt) { + BadKbConfig* cfg = bad_kb->set_bt_id ? &bad_kb->id_config : &bad_kb->config; + item = variable_item_list_add( var_item_list, "BT Remember", 2, bad_kb_scene_config_bt_remember_callback, bad_kb); - variable_item_set_current_value_index(item, bad_kb->bt_remember); - variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF"); + variable_item_set_current_value_index(item, cfg->ble.bonding); + variable_item_set_current_value_text(item, cfg->ble.bonding ? "ON" : "OFF"); + + item = variable_item_list_add( + var_item_list, + "BT Pairing", + GapPairingCount, + bad_kb_scene_config_bt_pairing_callback, + bad_kb); + variable_item_set_current_value_index(item, cfg->ble.pairing); + variable_item_set_current_value_text(item, bt_pairing_names[cfg->ble.pairing]); item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb); item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb); - if(bad_kb->bt_remember) { + if(cfg->ble.bonding) { variable_item_set_locked(item, true, "Remember\nmust be Off!"); } item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_kb); - if(bad_kb->bt_remember) { + if(cfg->ble.bonding) { variable_item_set_locked(item, true, "Remember\nmust be Off!"); } } else { @@ -109,6 +145,9 @@ bool bad_kb_scene_config_on_event(void* context, SceneManagerEvent event) { case VarItemListIndexBtRemember: bad_kb_config_refresh(bad_kb); break; + case VarItemListIndexBtPairing: + bad_kb_config_refresh(bad_kb); + break; case VarItemListIndexBtDeviceName: scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtName); break; diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index 79f6791b3b..f3fd0fff77 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -17,5 +17,6 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) +ADD_SCENE(ibutton, read_exit_confirm, ReadExitConfirm) ADD_SCENE(ibutton, view_data, ViewData) ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index b293af952c..a7eba672a3 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -7,23 +7,33 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { Widget* widget = ibutton->widget; FuriString* tmp = furi_string_alloc(); + FuriString* uid = furi_string_alloc(); widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context); widget_add_button_element( widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name); + furi_string_printf(tmp, "\e#Delete %s?\e#\n", ibutton->key_name); + + ibutton_protocols_render_uid(ibutton->protocols, key, uid); + + furi_string_cat_printf( + uid, + "\n%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, ibutton_key_get_protocol_id(key)), + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + + furi_string_cat(tmp, uid); + widget_add_text_box_element( - widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false); + widget, 0, 0, 128, 64, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + furi_string_reset(uid); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(uid); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 3cfddb8d04..070f229109 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -21,17 +21,20 @@ void ibutton_scene_emulate_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "[%s]\n%s", - ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)), - furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, + "Unsaved\n%s", + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); widget_add_string_multiline_element( - widget, 88, 5, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "Emulating"); ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->worker, key); diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index 50dc328a5f..ba72de6ca7 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -8,19 +8,24 @@ void ibutton_scene_info_on_enter(void* context) { const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); FuriString* tmp = furi_string_alloc(); - FuriString* keynumber = furi_string_alloc(); - - ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber); + FuriString* brief_data = furi_string_alloc(); furi_string_printf( tmp, - "\e#%s\n[%s]\e#\n%s", + "Name:%s\n\e#%s %s\e#\n", ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, protocol_id), - furi_string_get_cstr(keynumber)); + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + + ibutton_protocols_render_brief_data(ibutton->protocols, key, brief_data); + + furi_string_cat(tmp, brief_data); widget_add_text_box_element( - widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); + widget, 0, 0, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), false); + + furi_string_reset(tmp); + furi_string_reset(brief_data); if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) & iButtonProtocolFeatureExtData) { @@ -30,7 +35,7 @@ void ibutton_scene_info_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); - furi_string_free(keynumber); + furi_string_free(brief_data); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index f360c3ac43..f490488f5f 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -12,9 +12,9 @@ void ibutton_scene_read_on_enter(void* context) { iButtonKey* key = ibutton->key; iButtonWorker* worker = ibutton->worker; - popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); - popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); + popup_set_header(popup, "Reading", 95, 26, AlignCenter, AlignBottom); + popup_set_text(popup, "Connect key\nwith pogo pins", 95, 30, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 10, &I_DolphinWait_59x54); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_error.c index e966384bfc..6655657521 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_error.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_error.c @@ -14,13 +14,10 @@ void ibutton_scene_read_error_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); - widget_add_string_element( - widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error"); - ibutton_protocols_render_error(ibutton->protocols, key, tmp); - widget_add_string_multiline_element( - widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget_add_text_box_element( + widget, 0, 0, 128, 48, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_notification_message(ibutton, iButtonNotificationMessageError); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c new file mode 100644 index 0000000000..4077de9a63 --- /dev/null +++ b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c @@ -0,0 +1,59 @@ +#include "../ibutton_i.h" + +static void ibutton_scene_read_exit_confirm_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + iButton* ibutton = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); + } +} + +void ibutton_scene_read_exit_confirm_on_enter(void* context) { + iButton* ibutton = context; + Widget* widget = ibutton->widget; + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Exit", + ibutton_scene_read_exit_confirm_widget_callback, + ibutton); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Stay", + ibutton_scene_read_exit_confirm_widget_callback, + ibutton); + widget_add_string_element( + widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); + widget_add_string_element( + widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); +} + +bool ibutton_scene_read_exit_confirm_on_event(void* context, SceneManagerEvent event) { + iButton* ibutton = context; + SceneManager* scene_manager = ibutton->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + consumed = true; // Ignore Back button presses + } else if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeLeft) { + scene_manager_search_and_switch_to_previous_scene(scene_manager, iButtonSceneRead); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void ibutton_scene_read_exit_confirm_on_exit(void* context) { + iButton* ibutton = context; + widget_reset(ibutton->widget); +} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 1555f2cc20..890e0a2848 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -30,29 +30,29 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { ibutton_scene_read_key_menu_submenu_callback, ibutton); - if(features & iButtonProtocolFeatureExtData) { + if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( submenu, - "View Data", - SubmenuIndexViewData, + "Write ID", + SubmenuIndexWriteBlank, ibutton_scene_read_key_menu_submenu_callback, ibutton); } - if(features & iButtonProtocolFeatureWriteBlank) { + if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( submenu, - "Write Blank", - SubmenuIndexWriteBlank, + "Full Write on Same Type", + SubmenuIndexWriteCopy, ibutton_scene_read_key_menu_submenu_callback, ibutton); } - if(features & iButtonProtocolFeatureWriteCopy) { + if(features & iButtonProtocolFeatureExtData) { submenu_add_item( submenu, - "Write Copy", - SubmenuIndexWriteCopy, + "Data Info", + SubmenuIndexViewData, ibutton_scene_read_key_menu_submenu_callback, ibutton); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 2e50bc9964..6dd06ca526 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -18,9 +18,9 @@ void ibutton_scene_read_success_on_enter(void* context) { furi_string_printf( tmp, - "%s[%s]", - ibutton_protocols_get_name(ibutton->protocols, protocol_id), - ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id)); + "%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); widget_add_string_element( widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); @@ -44,7 +44,7 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeBack) { consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index fc0cf42e2f..1547a647bf 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -21,12 +21,16 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) { if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( - submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); + submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); } if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( - submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton); + submenu, + "Full Write on Same Type", + SubmenuIndexWriteCopy, + ibutton_submenu_callback, + ibutton); } submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 80bf13447a..465b063010 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -40,30 +40,29 @@ void ibutton_scene_write_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "[%s]\n%s ", - ibutton_protocols_get_name(ibutton->protocols, protocol_id), - ibutton->key_name); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, "Unsaved\n%s", ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); - furi_string_set(tmp, "iButton\nwriting "); - if(ibutton->write_mode == iButtonWriteModeBlank) { - furi_string_cat(tmp, "Blank"); + furi_string_set(tmp, "Writing ID"); ibutton_worker_write_blank_start(worker, key); } else if(ibutton->write_mode == iButtonWriteModeCopy) { - furi_string_cat(tmp, "Copy"); + furi_string_set(tmp, "Full Writing"); ibutton_worker_write_copy_start(worker, key); } widget_add_string_multiline_element( - widget, 88, 5, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index eb773f41b1..b0e373ea52 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -15,8 +15,10 @@ void lfrfid_scene_read_success_on_enter(void* context) { } else { furi_string_printf(display_text, "\e#%s\e#", protocol); } + widget_add_text_box_element( + widget, 16, 2, 112, 14, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); - furi_string_cat(display_text, "\nHex: "); + furi_string_set(display_text, "Hex: "); const size_t data_size = protocol_dict_get_data_size(app->dict, app->protocol_id); uint8_t* data = malloc(data_size); @@ -40,7 +42,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { furi_string_free(rendered_data); widget_add_text_box_element( - widget, 0, 0, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); + widget, 0, 16, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c index 70c47e8a43..2f223ae87f 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_name.c @@ -36,7 +36,7 @@ void lfrfid_scene_save_name_on_enter(void* context) { LFRFID_TEXT_STORE_SIZE, key_name_is_empty); - FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), app->text_store); + FURI_LOG_D("", "%s %s", furi_string_get_cstr(folder_path), app->text_store); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( furi_string_get_cstr(folder_path), diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_config.h b/applications/main/momentum_app/scenes/momentum_app_scene_config.h index 77eb464325..419d9b27d8 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_config.h +++ b/applications/main/momentum_app/scenes/momentum_app_scene_config.h @@ -18,6 +18,7 @@ ADD_SCENE(momentum_app, misc, Misc) ADD_SCENE(momentum_app, misc_screen, MiscScreen) ADD_SCENE(momentum_app, misc_screen_color, MiscScreenColor) ADD_SCENE(momentum_app, misc_dolphin, MiscDolphin) +ADD_SCENE(momentum_app, misc_spoof, MiscSpoof) +ADD_SCENE(momentum_app, misc_spoof_name, MiscSpoofName) ADD_SCENE(momentum_app, misc_vgm, MiscVgm) ADD_SCENE(momentum_app, misc_vgm_color, MiscVgmColor) -ADD_SCENE(momentum_app, misc_rename, MiscRename) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc.c index ba9c1077e2..64b9900f2b 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc.c @@ -3,8 +3,8 @@ enum VarItemListIndex { VarItemListIndexScreen, VarItemListIndexDolphin, + VarItemListIndexSpoof, VarItemListIndexVgm, - VarItemListIndexChangeDeviceName, VarItemListIndexChargeCap, VarItemListIndexShowMomentumIntro, }; @@ -37,10 +37,11 @@ void momentum_app_scene_misc_on_enter(void* context) { item = variable_item_list_add(var_item_list, "Dolphin", 0, NULL, app); variable_item_set_current_value_text(item, ">"); - item = variable_item_list_add(var_item_list, "VGM Options", 0, NULL, app); + item = variable_item_list_add(var_item_list, "Spoofing Options", 0, NULL, app); variable_item_set_current_value_text(item, ">"); - variable_item_list_add(var_item_list, "Change Device Name", 0, NULL, app); + item = variable_item_list_add(var_item_list, "VGM Options", 0, NULL, app); + variable_item_set_current_value_text(item, ">"); char cap_str[6]; value_index = momentum_settings.charge_cap / CHARGE_CAP_INTV; @@ -81,14 +82,14 @@ bool momentum_app_scene_misc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscDolphin, 0); scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscDolphin); break; + case VarItemListIndexSpoof: + scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof, 0); + scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscSpoof); + break; case VarItemListIndexVgm: scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscVgm, 0); scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscVgm); break; - case VarItemListIndexChangeDeviceName: - scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscRename, 0); - scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscRename); - break; case VarItemListIndexShowMomentumIntro: { for(int i = 0; i < 10; i++) { if(storage_common_copy( diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c new file mode 100644 index 0000000000..a9aea7efb7 --- /dev/null +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof.c @@ -0,0 +1,77 @@ +#include "../momentum_app.h" + +enum VarItemListIndex { + VarItemListIndexFlipperName, // TODO: Split into name, mac, serial + VarItemListIndexShellColor, +}; + +const char* const shell_color_names[FuriHalVersionColorCount] = { + "Real", + "Black", + "White", + "Transparent", +}; +static void momentum_app_scene_misc_spoof_shell_color_changed(VariableItem* item) { + MomentumApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, shell_color_names[index]); + momentum_settings.spoof_color = index; + app->save_settings = true; + app->require_reboot = true; +} + +void momentum_app_scene_misc_spoof_var_item_list_callback(void* context, uint32_t index) { + MomentumApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void momentum_app_scene_misc_spoof_on_enter(void* context) { + MomentumApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "Flipper Name", 0, NULL, app); + variable_item_set_current_value_text(item, app->device_name); + + item = variable_item_list_add( + var_item_list, + "Shell Color", + FuriHalVersionColorCount, + momentum_app_scene_misc_spoof_shell_color_changed, + app); + variable_item_set_current_value_index(item, momentum_settings.spoof_color); + variable_item_set_current_value_text(item, shell_color_names[momentum_settings.spoof_color]); + + variable_item_list_set_enter_callback( + var_item_list, momentum_app_scene_misc_spoof_var_item_list_callback, app); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof)); + + view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewVarItemList); +} + +bool momentum_app_scene_misc_spoof_on_event(void* context, SceneManagerEvent event) { + MomentumApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneMiscSpoof, event.event); + consumed = true; + switch(event.event) { + case VarItemListIndexFlipperName: + scene_manager_next_scene(app->scene_manager, MomentumAppSceneMiscSpoofName); + break; + default: + break; + } + } + + return consumed; +} + +void momentum_app_scene_misc_spoof_on_exit(void* context) { + MomentumApp* app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c similarity index 70% rename from applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c rename to applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c index 5f0127dfd2..1ba6aed7bd 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_misc_rename.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_misc_spoof_name.c @@ -4,7 +4,7 @@ enum TextInputIndex { TextInputResultOk, }; -static void momentum_app_scene_misc_rename_text_input_callback(void* context) { +static void momentum_app_scene_misc_spoof_name_text_input_callback(void* context) { MomentumApp* app = context; app->save_name = true; @@ -12,8 +12,10 @@ static void momentum_app_scene_misc_rename_text_input_callback(void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); } -static bool - momentum_app_scene_misc_rename_validator(const char* text, FuriString* error, void* context) { +static bool momentum_app_scene_misc_spoof_name_validator( + const char* text, + FuriString* error, + void* context) { UNUSED(context); for(; *text; ++text) { @@ -27,19 +29,19 @@ static bool return true; } -void momentum_app_scene_misc_rename_on_enter(void* context) { +void momentum_app_scene_misc_spoof_name_on_enter(void* context) { MomentumApp* app = context; TextInput* text_input = app->text_input; - text_input_set_header_text(text_input, "Leave empty for default"); + text_input_set_header_text(text_input, "Leave empty for real name"); - text_input_set_validator(text_input, momentum_app_scene_misc_rename_validator, NULL); + text_input_set_validator(text_input, momentum_app_scene_misc_spoof_name_validator, NULL); text_input_set_minimum_length(text_input, 0); text_input_set_result_callback( text_input, - momentum_app_scene_misc_rename_text_input_callback, + momentum_app_scene_misc_spoof_name_text_input_callback, app, app->device_name, FURI_HAL_VERSION_ARRAY_NAME_LENGTH, @@ -48,7 +50,7 @@ void momentum_app_scene_misc_rename_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewTextInput); } -bool momentum_app_scene_misc_rename_on_event(void* context, SceneManagerEvent event) { +bool momentum_app_scene_misc_spoof_name_on_event(void* context, SceneManagerEvent event) { MomentumApp* app = context; bool consumed = false; @@ -66,7 +68,7 @@ bool momentum_app_scene_misc_rename_on_event(void* context, SceneManagerEvent ev return consumed; } -void momentum_app_scene_misc_rename_on_exit(void* context) { +void momentum_app_scene_misc_spoof_name_on_exit(void* context) { MomentumApp* app = context; text_input_reset(app->text_input); } diff --git a/applications/main/nfc/helpers/felica_auth.c b/applications/main/nfc/helpers/felica_auth.c new file mode 100644 index 0000000000..a8cd0929ba --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.c @@ -0,0 +1,21 @@ +#include "felica_auth.h" + +FelicaAuthenticationContext* felica_auth_alloc() { + FelicaAuthenticationContext* instance = malloc(sizeof(FelicaAuthenticationContext)); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + return instance; +} + +void felica_auth_free(FelicaAuthenticationContext* instance) { + furi_assert(instance); + free(instance); +} + +void felica_auth_reset(FelicaAuthenticationContext* instance) { + furi_assert(instance); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + instance->auth_status.external = 0; + instance->auth_status.internal = 0; +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/felica_auth.h b/applications/main/nfc/helpers/felica_auth.h new file mode 100644 index 0000000000..3d99f1f9cd --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FelicaAuthenticationContext* felica_auth_alloc(); + +void felica_auth_free(FelicaAuthenticationContext* instance); + +void felica_auth_reset(FelicaAuthenticationContext* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index affa33b865..b0660f3e62 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -7,6 +7,11 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, +}; static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -18,6 +23,35 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + furi_string_free(temp_str); +} + +static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + return true; + } + + return false; +} + +static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + + nfc_render_felica_dump(data, temp_str); + widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); @@ -29,29 +63,75 @@ static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, v NfcApp* instance = context; const FelicaPollerEvent* felica_event = event.event_data; + NfcCommand command = NfcCommandContinue; if(felica_event->type == FelicaPollerEventTypeReady) { nfc_device_set_data( instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if( + felica_event->type == FelicaPollerEventTypeError || + felica_event->type == FelicaPollerEventTypeIncomplete) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + FelicaAuthenticationContext* ctx = felica_event->data->auth_context; + ctx->skip_auth = instance->felica_auth->skip_auth; + memcpy(ctx->card_key.data, instance->felica_auth->card_key.data, FELICA_DATA_BLOCK_SIZE); } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); } +bool nfc_scene_read_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + nfc_unlock_helper_card_detected_handler(instance); + } else if(event.event == NfcCustomEventPollerIncomplete) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + } + return true; +} + static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); - nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn)) { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + } else { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); + } + felica_auth_reset(instance->felica_auth); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); @@ -74,23 +154,50 @@ static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } +static void nfc_scene_read_menu_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + if(data->blocks_read != data->blocks_total) { + submenu_add_item( + instance->submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaKeyInput); + return true; + } + } + return false; +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { .features = NfcProtocolFeatureEmulateUid, .scene_info = { .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_scene_info_on_event_felica, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = { .on_enter = nfc_scene_read_on_enter_felica, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_felica, }, .scene_read_menu = { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_enter = nfc_scene_read_menu_on_enter_felica, + .on_event = nfc_scene_read_menu_on_event_felica, }, .scene_read_success = { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 3142b2c6db..6c57fb24b6 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -1,19 +1,107 @@ #include "felica_render.h" -void nfc_render_felica_info( +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification) { + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } +} + +void nfc_render_felica_idm( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str) { - furi_string_cat_printf(str, "IDm:"); + furi_string_cat_printf(str, (format_type == NfcProtocolFormatTypeFull) ? "IDm:\n" : "IDm:"); for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { - furi_string_cat_printf(str, " %02X", data->idm.data[i]); + furi_string_cat_printf( + str, + (format_type == NfcProtocolFormatTypeFull) ? "%02X " : " %02X", + data->idm.data[i]); + } +} + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + nfc_render_felica_idm(data, format_type, str); + if(format_type == NfcProtocolFormatTypeFull) { - furi_string_cat_printf(str, "\nPMm:"); + furi_string_cat_printf(str, "\nPMm:\n"); for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { - furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); + } + } + nfc_render_felica_blocks_count(data, str, true); +} + +static void nfc_render_felica_block_name( + const char* name, + FuriString* str, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + +static void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { + furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); + furi_string_cat_printf(str, "%02X ", block->data[j]); + } + furi_string_cat_printf(str, "\n"); +} + +static void nfc_render_felica_block( + const FelicaBlock* block, + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + nfc_render_felica_block_name(name, str, prefix_separator_cnt, suffix_separator_cnt); + nfc_render_felica_block_data(block, str); +} + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { + FuriString* name = furi_string_alloc(); + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 6d9816fc66..3d32e8d14e 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -4,7 +4,19 @@ #include "../nfc_protocol_support_render_common.h" +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification); + void nfc_render_felica_info( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str); + +void nfc_render_felica_idm( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index ef51d98e0b..deba1bca28 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -34,6 +34,8 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolMfDesfire); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; const MfDesfirePollerEvent* mf_desfire_event = event.event_data; @@ -41,10 +43,12 @@ static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent even nfc_device_set_data( instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) { + command = NfcCommandReset; } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 94b333f552..f8eacd51a2 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -11,24 +11,29 @@ void nfc_render_mf_desfire_info( const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; - furi_string_cat_printf(str, "\n%lu", bytes_total); + if(data->master_key_settings.is_free_directory_list) { + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(app->key_settings.is_free_directory_list) { + file_count += simple_array_get_count(app->file_ids); + } + } - if(data->version.sw_storage & 1) { - furi_string_push_back(str, '+'); + furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + } else { + furi_string_cat_printf(str, "\nAuth required to read apps!"); } - furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); - - const uint32_t app_count = simple_array_get_count(data->applications); - uint32_t file_count = 0; + furi_string_cat_printf(str, "\n%lu", bytes_total); - for(uint32_t i = 0; i < app_count; ++i) { - const MfDesfireApplication* app = simple_array_cget(data->applications, i); - file_count += simple_array_get_count(app->file_ids); + if(data->version.sw_storage & 1) { + furi_string_push_back(str, '+'); } - - furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); - furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free); if(format_type != NfcProtocolFormatTypeFull) return; @@ -101,17 +106,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri } void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { - furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); - furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); - furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); - furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); - furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + } else { + furi_string_cat_printf(str, "changeKeyID ??\n"); + furi_string_cat_printf(str, "configChangeable ??\n"); + furi_string_cat_printf(str, "freeCreateDelete ??\n"); + furi_string_cat_printf(str, "freeDirectoryList 0\n"); + furi_string_cat_printf(str, "masterChangeable ??\n"); + } if(data->flags) { furi_string_cat_printf(str, "flags %d\n", data->flags); } - furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + } else { + furi_string_cat_printf(str, "maxKeys ??\n"); + } } void nfc_render_mf_desfire_key_version( @@ -123,14 +140,16 @@ void nfc_render_mf_desfire_key_version( void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { const uint8_t* app_id = data->data; - furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]); } void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { nfc_render_mf_desfire_key_settings(&data->key_settings, str); - for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { - nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + if(data->key_settings.is_free_directory_list) { + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } } } @@ -179,13 +198,16 @@ void nfc_render_mf_desfire_file_settings_data( } furi_string_cat_printf(str, "%s %s\n", type, comm); - furi_string_cat_printf( - str, - "r %d w %d rw %d c %d\n", - settings->access_rights >> 12 & 0xF, - settings->access_rights >> 8 & 0xF, - settings->access_rights >> 4 & 0xF, - settings->access_rights & 0xF); + + for(size_t i = 0; i < settings->access_rights_len; i++) { + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights[i] >> 12 & 0xF, + settings->access_rights[i] >> 8 & 0xF, + settings->access_rights[i] >> 4 & 0xF, + settings->access_rights[i] & 0xF); + } uint32_t record_count = 1; uint32_t record_size = 0; @@ -217,6 +239,20 @@ void nfc_render_mf_desfire_file_settings_data( break; } + bool is_auth_required = true; + for(size_t i = 0; i < settings->access_rights_len; i++) { + uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f; + uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f; + if((read_rights == 0x0e) || (read_write_rights == 0x0e)) { + is_auth_required = false; + break; + } + } + if(is_auth_required) { + furi_string_cat_printf(str, "Auth required to read file data\n"); + return; + } + if(simple_array_get_count(data->data) == 0) { return; } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index bd2015889d..cd16374bc8 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -8,6 +8,7 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, @@ -157,48 +158,15 @@ static NfcCommand return NfcCommandContinue; } -enum { - NfcSceneMfUltralightReadMenuStateCardSearch, - NfcSceneMfUltralightReadMenuStateCardFound, -}; - -static void nfc_scene_read_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); - - if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); - popup_set_text( - instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); - } else { - popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); - popup_set_icon(instance->popup, 12, 20, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { - bool unlocking = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); - - uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : - NfcSceneMfUltralightReadMenuStateCardFound; - - scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); - - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_card_detected_handler(instance); } else if((event.event == NfcCustomEventPollerIncomplete)) { notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 5f41a0d892..3cfbebf67c 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -574,7 +574,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64); + widget_add_icon_element(widget, 0, 0, &I_NFC_dolphin_emulation_51x64); if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c new file mode 100644 index 0000000000..f1d504d248 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c @@ -0,0 +1,38 @@ +#include "nfc_protocol_support_unlock_helper.h" + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_unlock_helper_setup_from_state(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene( + instance->scene_manager, NfcSceneMfUltralightUnlockWarn) || + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn); + + uint32_t state = unlocking ? NfcSceneReadMenuStateCardSearch : NfcSceneReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); +} + +void nfc_unlock_helper_card_detected_handler(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h new file mode 100644 index 0000000000..65da332402 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h @@ -0,0 +1,9 @@ +#include "nfc/nfc_app_i.h" + +typedef enum { + NfcSceneReadMenuStateCardSearch, + NfcSceneReadMenuStateCardFound, +} NfcSceneUnlockReadState; + +void nfc_unlock_helper_setup_from_state(NfcApp* instance); +void nfc_unlock_helper_card_detected_handler(NfcApp* instance); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index bcfe7d0374..7dca264090 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -51,6 +51,7 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + instance->felica_auth = felica_auth_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); @@ -142,6 +143,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); + felica_auth_free(instance->felica_auth); mf_ultralight_auth_free(instance->mf_ul_auth); slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index df11c985f4..d38e3eab6c 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -33,6 +33,7 @@ #include "helpers/nfc_emv_parser.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" #include @@ -130,6 +131,7 @@ struct NfcApp { NfcScanner* scanner; NfcListener* listener; + FelicaAuthenticationContext* felica_auth; MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; diff --git a/applications/main/nfc/plugins/supported_cards/all_in_one.c b/applications/main/nfc/plugins/supported_cards/all_in_one.c index 9e20b92d1f..f85d7ad2e8 100644 --- a/applications/main/nfc/plugins/supported_cards/all_in_one.c +++ b/applications/main/nfc/plugins/supported_cards/all_in_one.c @@ -20,8 +20,8 @@ static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { const uint8_t layout_byte = data->page[5].data[2]; const uint8_t layout_half_byte = data->page[5].data[2] & 0x0F; - FURI_LOG_I(TAG, "Layout byte: %02x", layout_byte); - FURI_LOG_I(TAG, "Layout half-byte: %02x", layout_half_byte); + FURI_LOG_D(TAG, "Layout byte: %02x", layout_byte); + FURI_LOG_D(TAG, "Layout half-byte: %02x", layout_half_byte); switch(layout_half_byte) { // If it is A, the layout type is a type A layout @@ -32,7 +32,7 @@ static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) { case 0x02: return AllInOneLayoutType2; default: - FURI_LOG_I(TAG, "Unknown layout type: %d", layout_half_byte); + FURI_LOG_E(TAG, "Unknown layout type: %d", layout_half_byte); return AllInOneLayoutTypeUnknown; } } @@ -47,7 +47,7 @@ static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { do { if(data->page[4].data[0] != 0x45 || data->page[4].data[1] != 0xD9) { - FURI_LOG_I(TAG, "Pass not verified"); + FURI_LOG_E(TAG, "Pass not verified"); break; } @@ -63,7 +63,7 @@ static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) { // If the layout is D, the ride count is stored in the second byte of page 9 ride_count = data->page[9].data[1]; } else { - FURI_LOG_I(TAG, "Unknown layout: %d", layout_type); + FURI_LOG_E(TAG, "Unknown layout: %d", layout_type); ride_count = 137; } diff --git a/applications/main/nfc/plugins/supported_cards/charliecard.c b/applications/main/nfc/plugins/supported_cards/charliecard.c index 7a405fffb2..9880ca3ad7 100644 --- a/applications/main/nfc/plugins/supported_cards/charliecard.c +++ b/applications/main/nfc/plugins/supported_cards/charliecard.c @@ -23,29 +23,41 @@ * — Reverse engineer passes (sectors 4 & 5?), impl. * — Infer transaction flag meanings * — Infer remaining unknown bytes in the balance sectors (2 & 3) - * – ASCII art &/or unified read function for the balance sectors, - * to improve readability / interpretability by others? * — Improve string output formatting, esp. of transaction log + * — Mapping of buses to garages, and subsequently, route subsets via + * http://roster.transithistory.org/ data + * — Mapping of stations to lines + * — Add'l data fields for side of station fare gates are on? Some stations + * separate inbound & outbound sides, so direction could be inferred + * from gates used. * — Continually gather data on fare gate ID mappings, update as collected; * check locations this might be scrapable / inferrable from: * [X] MBTA GTFS spec (https://www.mbta.com/developers/gtfs) features & IDs * seem too-coarse-grained & uncorrelated - * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) + * [X] MBTA ArcGIS (https://mbta-massdot.opendata.arcgis.com/) & Tableau + * (https://public.tableau.com/app/profile/mbta.office.of.performance.management.and.innovation/vizzes) * files don't seem to have anything of that resolution (only down to ridership by station) * [X] (skim of) MBTA public GitHub (https://github.com/mbta) repos make no reference to fare-gate-level data * [X] (skim of) MBTA public engineering docs (https://www.mbta.com/engineering) unfruitful; - * Closest mention spotted is 2014 "Ridership and Service Statistics" (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) + * Closest mention spotted is 2014 "Ridership and Service Statistics" + * (https://cdn.mbta.com/sites/default/files/fmcb-meeting-docs/reports-policies/2014-07-mbta-bluebook-ed14.pdf) * where on pg.40, "Equipment at Stations" is enumerated, and fare gates counts are given, - * listed as "AFC Gates" (presumably standing for "Automated Fare Control") + * listed as "AFC Gates" (presumably standing for "Automated Fare Collection") * [X] Josiah Zachery criminal trial public evidence — convicted partially on * data on his CharlieCard, appeals partially on basis of legality of this search. * Prev. court case (gag order mentioned in preamble) leaked some data in the files * entered into evidence. Seemingly did not happen here; fare gate IDs unmentioned, * only ever the nature of stored/saved data and methods of retrieval. - * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 (https://www.ma-appellatecourts.org/party) - * Trial court case 04/02/2015 #1584CR10265 @Suffolk County Criminal Superior Court (https://www.masscourts.org/eservices/home.page.16) - * [ ] FOIA / public records request? (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) - * [ ] MBTA data blog? (https://www.massdottracker.com/datablog/) + * Appelate case dockets 2019-P-0401, SJC-12952, SJ-2017-0390 + * (https://www.ma-appellatecourts.org/party) + * Trial court indictment 04/02/2015, Case# 1584CR10265 @Suffolk County Criminal Superior Court + * (https://www.masscourts.org/eservices/home.page.16) + * [ ] FOIA / public records request? + * (https://massachusettsdot.mycusthelp.com/WEBAPP/_rs/(S(tbcygdlm0oojy35p1wv0y2y5))/supporthome.aspx) + * [X] MBTA data blog? (https://www.massdottracker.com/datablog/) + * [ ] MassDOT developers Google group? (https://groups.google.com/g/massdotdevelopers) + * [X] preexisting posts + * [ ] ask directly? * [ ] Other? * * This program is free software: you can redistribute it and/or modify it @@ -82,12 +94,8 @@ // timestep is one minute #define CHARLIE_TIME_DELTA_SECS 60 #define CHARLIE_END_VALID_DELTA_SECS 60 * 8 -#define CHARLIE_N_TRIP_HISTORY 10 - -enum CharlieActiveSector { - CHARLIE_ACTIVE_SECTOR_2, - CHARLIE_ACTIVE_SECTOR_3, -}; +#define CHARLIE_N_TRANSACTION_HISTORY 10 +#define CHARLIE_N_PASSES 4 typedef struct { uint64_t a; @@ -138,8 +146,27 @@ typedef struct { uint16_t gate; uint8_t g_flag; Money fare; - uint8_t f_flag; -} Trip; + uint16_t f_flag; +} Transaction; + +typedef struct { + bool valid; + uint16_t pre; + uint16_t post; + DateTime date; +} Pass; + +typedef struct { + uint16_t n_uses; + uint8_t active_balance_sector; +} CounterSector; + +typedef struct { + Money balance; + uint16_t type; + DateTime issued; + DateTime end_validity; +} BalanceSector; // IdMapping approach borrowed from Jeremy Cooper's 'clipper.c' typedef struct { @@ -147,14 +174,14 @@ typedef struct { const char* name; } IdMapping; -// this should be a complete accounting of types, +// this should be a complete accounting of types, (1 and 7 day pass types maybe missing?) static const IdMapping charliecard_types[] = { // Regular card types {.id = 367, .name = "Adult"}, {.id = 366, .name = "SV Adult"}, {.id = 418, .name = "Student"}, {.id = 419, .name = "Senior"}, - {.id = 420, .name = "Tap"}, + {.id = 420, .name = "TAP"}, {.id = 417, .name = "Blind"}, {.id = 426, .name = "Child"}, {.id = 410, .name = "Employee ID Without Passback"}, @@ -164,16 +191,16 @@ static const IdMapping charliecard_types[] = { // Passes {.id = 135, .name = "30 Day Local Bus Pass"}, - {.id = 136, .name = "30 Day Inner Express Bus Pass"}, // - {.id = 137, .name = "30 Day Outer Express Bus Pass"}, // - {.id = 138, .name = "30 Day LinkPass"}, // - {.id = 139, .name = "30 Day Senior LinkPass"}, // + {.id = 136, .name = "30 Day Inner Express Bus Pass"}, + {.id = 137, .name = "30 Day Outer Express Bus Pass"}, + {.id = 138, .name = "30 Day LinkPass"}, + {.id = 139, .name = "30 Day Senior LinkPass"}, {.id = 148, .name = "30 Day TAP LinkPass"}, {.id = 150, .name = "Monthly Student LinkPass"}, - {.id = 424, .name = "Monthly TAP LinkPass"}, // 0b0110101000 - {.id = 425, .name = "Monthly Senior LinkPass"}, // 0b0110101001 - {.id = 421, .name = "Senior TAP/Permit"}, // 0b0110100101 - {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // 0b0110100110 + {.id = 424, .name = "Monthly TAP LinkPass"}, + {.id = 425, .name = "Monthly Senior LinkPass"}, + {.id = 421, .name = "Senior TAP/Permit"}, + {.id = 422, .name = "Senior TAP/Permit 30 Days"}, // Commuter rail passes {.id = 166, .name = "30 Day Commuter Rail Zone 1A Pass"}, @@ -388,9 +415,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 6647, .name = "Malden Center"}, {.id = 6648, .name = "Malden Center"}, // Chinatown - {.id = 6704, - .name = - "Malden Center"}, // Entry error? Placed after "Chinatown" divider, but with name Malden Center + {.id = 6704, .name = "Chinatown"}, {.id = 6705, .name = "Chinatown"}, {.id = 2099, .name = "Chinatown"}, {.id = 7003, .name = "Chinatown"}, @@ -471,7 +496,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 7016, .name = "Forest Hills"}, {.id = 6950, .name = "Forest Hills"}, {.id = 6951, .name = "Forest Hills"}, - {.id = 604, .name = "Forest Hills"}, // Entry error? + {.id = 604, .name = "Forest Hills"}, {.id = 7096, .name = "Forest Hills"}, // South Station {.id = 7039, .name = "South Station"}, @@ -546,7 +571,7 @@ static const IdMapping charliecard_fare_gate_ids[] = { {.id = 2010, .name = "Wood Island"}, {.id = 6971, .name = "Wood Island"}, // Orient Heights - {.id = 6621, .name = "Orient Heights"}, // marked as needs checking + {.id = 6621, .name = "Orient Heights"}, {.id = 6622, .name = "Orient Heights"}, {.id = 6623, .name = "Orient Heights"}, {.id = 2014, .name = "Orient Heights"}, @@ -578,8 +603,13 @@ static const IdMapping charliecard_fare_gate_ids[] = { }; static const size_t kNumFareGateIds = COUNT_OF(charliecard_fare_gate_ids); +// ********************************************************** +// ********************* MISC HELPERS *********************** +// ********************************************************** + static const uint8_t* pos_to_ptr(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // returns pointer to specified sector/block/byte of MFClassic card data uint8_t block_offset = mf_classic_get_first_block_num_of_sector(sector_num); return &data->block[block_offset + block_num].data[byte_num]; } @@ -590,16 +620,29 @@ static uint64_t pos_to_num( uint8_t block_num, uint8_t byte_num, uint8_t byte_len) { + // returns numeric values at specified card location, for given byte length. + // assumes big endian. return bit_lib_bytes_to_num_be(pos_to_ptr(data, sector_num, block_num, byte_num), byte_len); } static DateTime dt_delta(DateTime dt, uint64_t delta_secs) { + // returns shifted DateTime, from initial DateTime and time offset in seconds DateTime dt_shifted = {0}; datetime_timestamp_to_datetime(datetime_datetime_to_timestamp(&dt) + delta_secs, &dt_shifted); return dt_shifted; } +static bool dt_ge(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); +} + +static bool dt_eq(DateTime dt1, DateTime dt2) { + // compares two DateTimes + return datetime_datetime_to_timestamp(&dt1) == datetime_datetime_to_timestamp(&dt2); +} + static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) { // code borrowed from Jeremy Cooper's 'clipper.c'. Used as follows: // const char* s; if(!get_map_item(_,_,_,&s)) {s="Default str";} @@ -614,82 +657,17 @@ static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const cha return false; } -static bool charliecard_verify(Nfc* nfc) { - // does this suffice? Or should I check add'l keys/data/etc? - bool verified = false; - - do { - const uint8_t verify_sector = 1; - const uint8_t verify_block = mf_classic_get_first_block_num_of_sector(verify_sector) + 1; - FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); - - MfClassicKey key = {0}; - bit_lib_num_to_bytes_be( - charliecard_1k_keys[verify_sector].a, COUNT_OF(key.data), key.data); - - MfClassicAuthContext auth_context; - MfClassicError error = - mf_classic_poller_sync_auth(nfc, verify_block, &key, MfClassicKeyTypeA, &auth_context); - if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", verify_block, error); - break; - } - - verified = true; - } while(false); - - return verified; +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); } -static bool charliecard_read(Nfc* nfc, NfcDevice* device) { - furi_assert(nfc); - furi_assert(device); - - bool is_read = false; - - MfClassicData* data = mf_classic_alloc(); - nfc_device_copy_data(device, NfcProtocolMfClassic, data); - - do { - MfClassicType type = MfClassicTypeMini; - MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); - if(error != MfClassicErrorNone) break; - - data->type = type; - if(type != MfClassicType1k) break; - - MfClassicDeviceKeys keys = { - .key_a_mask = 0, - .key_b_mask = 0, - }; - for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - bit_lib_num_to_bytes_be( - charliecard_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); - FURI_BIT_SET(keys.key_a_mask, i); - bit_lib_num_to_bytes_be( - charliecard_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); - FURI_BIT_SET(keys.key_b_mask, i); - } - - error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error == MfClassicErrorNotPresent) { - FURI_LOG_W(TAG, "Failed to read data"); - break; - } - - nfc_device_set_data(device, NfcProtocolMfClassic, data); - - is_read = (error == MfClassicErrorNone); - } while(false); - - mf_classic_free(data); - - return is_read; +static bool is_debug() { + return furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); } -uint32_t time_now() { - return furi_hal_rtc_get_timestamp(); -} +// ********************************************************** +// ******************** FIELD PARSING *********************** +// ********************************************************** static Money money_parse( const MfClassicData* data, @@ -709,83 +687,249 @@ static DateTime return dt_delta(CHARLIE_EPOCH, ts_charlie * CHARLIE_TIME_DELTA_SECS); } -static DateTime - end_validity_parse(const MfClassicData* data, enum CharlieActiveSector active_sec) { - // End validity field is a bit odd; shares byte 1 with another variable (the card type field), - // occupying only the last 3 bits (and subsequent two bytes), hence bitmask - // TODO; what are the add'l 3 bits between type & end validity fields? - uint32_t ts_charlie_ev = - pos_to_num(data, (active_sec == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3, 1, 1, 3); - ts_charlie_ev = ts_charlie_ev & 0x1FFFFF; +static DateTime end_validity_parse( + const MfClassicData* data, + uint8_t sector_num, + uint8_t block_num, + uint8_t byte_num) { + // End validity field is weird; shares first byte with another variable (the card type field), + // occupying the last 5 bits (and subsequent two bytes), hence bitmask + uint32_t ts_charlie_ev = pos_to_num(data, sector_num, block_num, byte_num, 3) & 0x1FFFFF; // additionally, instead of minute deltas, is in 8 minute increments // relative to CHARLIE_EPOCH (2003/1/1), per DEFCON31 researcher's work return dt_delta(CHARLIE_EPOCH, ts_charlie_ev * CHARLIE_END_VALID_DELTA_SECS); } -static Trip - trip_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { - /* This function parses individual trips. Each trip packs 7 bytes, stored as follows: - - 0 1 2 3 4 5 6 - +----.----.----+----.--+-+----.----+ - | date | loc |f| amt | - +----.----.----+----.--+-+----.----+ - - Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. - Amount appears to contain some flag bits, however, it is unclear what precisely their function is. - - Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). - Least significant flag bit (ie "loc & 0x1") seems to indicate: - — When 0, fare (the amount by which balance is decremented) - — When 1, refill (the amount by which balance is incremented) - - On monthly pass cards, MSB of amt will be set: 0x8000 (negative zero) - Seemingly randomly (irrespective of card type, last trip, etc) 0x0001 will be set on amt in addition to - whatever the regular fare is (a half cent more). I am uncertain what this flag indicates. - */ - const DateTime date = date_parse(data, sector_num, block_num, byte_num); - const uint16_t gate = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 3; - const uint8_t g_flag = pos_to_num(data, sector_num, block_num, byte_num + 3, 2) & 0b111; - const Money fare = money_parse(data, sector_num, block_num, byte_num + 5); - const uint8_t f_flag = pos_to_num(data, sector_num, block_num, byte_num + 5, 2) & 0x8001; - return (Trip){date, gate, g_flag, fare, f_flag}; +static Pass + pass_parse(const MfClassicData* data, uint8_t sector_num, uint8_t block_num, uint8_t byte_num) { + // WIP; testing only. Speculating it may be structured as follows + // Sub-byte field divisions not drawn to scale, see code for exact bit offsets + // + // 0 1 2 3 4 5 + // +----.----.----.----+----.----+ + // | uk1 | date | uk2 | + // +----.----.----.----+----.----+ + // + // "Blank" entries are as follows: + // 0 1 2 3 4 5 + // +----.----.----.----.----.----+ + // | 00 20 00 00 00 00 | + // +----.----.----.----.----.----+ + // + // even when not blank, uk1 LSB seems to always be set to 1... + // the sole bit set to 1 on the blank entry seems to divide + // the uk1 and date fields, and is always set to 1 regardless + // same is true of type & end-validity split found in balance sector + // + // likely fields incl + // — type #, + // — a secondary date field (eg start/end, end validity or normal format) + // — ID of FVM from which the pass was loaded + + // check for empty, if so, return struct filled w/ 0s + // (incl "valid" field: hence, "valid" is false-y) + if(pos_to_num(data, sector_num, block_num, byte_num, 6) == 0x002000000000) { + return (Pass){0}; + } + + // const DateTime start = date_parse(data, sector_num, block_num, byte_num + 1); + + const uint16_t pre = pos_to_num(data, sector_num, block_num, byte_num, 2) >> 6; + const uint16_t post = (pos_to_num(data, sector_num, block_num, byte_num + 4, 2) >> 2) & 0x3ff; + + // these values make sense for a date, but implied position of type + // before end validity, as seen in balance sector, doesn't seem + // to produce sensible values + const DateTime date = end_validity_parse(data, sector_num, block_num, byte_num + 1); + + // DateTime start = date_parse(data, sector_num, block_num, byte_num); + // uint16_t type = 0; // pos_to_num(data, sector_num, block_num, byte_num + 3, 2) >> 6; + + return (Pass){true, pre, post, date}; } -static bool date_ge(DateTime dt1, DateTime dt2) { - return datetime_datetime_to_timestamp(&dt1) >= datetime_datetime_to_timestamp(&dt2); +static Transaction + transaction_parse(const MfClassicData* data, uint8_t sector, uint8_t block, uint8_t byte) { + // This function parses individual transactions. Each transaction packs 7 bytes, stored as follows: + // + // 0 1 2 3 4 5 6 + // +----.----.----+----.--+-+----.----+ + // | date | loc |f| amt | + // +----.----.----+----.--+-+----.----+ + // + // Where date is in the typical format, loc represents the fare gate tapped, and amt is the fare amount. + // Amount appears to contain some flag bits, however, it is unclear what precisely their function is. + // + // Gate ID ("loc") is only the first 13 bits of 0x3:0x5, the final three bits appear to be flags ("f"). + // Least significant flag bit seems to indicate: + // — When f & 1 == 1, fare (the amount by which balance is decremented) + // — When f & 1 == 0, refill (the amount by which balance is incremented) + // MSB (sign bit) of amt seems to serve the same role, just inverted, ie + // — When amt & 0x8000 == 0, fare + // — When amt & 0x8000 == 0x8000, refill + // Only contradiction between the two observed is on cards w/ passes; + // MSB of amt seems to be set for every transaction when (remaining bits of) amt is 0 on a card w/ a pass + // Hence, using f's LSB as method for inferring fare v. refill + // + // Remaining unknown bits: + // — f & 0b100; seems to be set on fares where the card has a pass, and amt is 0 + // — f & 0b010 + // — amt & 1; does not seem to correspond with card type, last transaction, first transaction, refill v. fare, etc + + const DateTime date = date_parse(data, sector, block, byte); + const uint16_t gate = pos_to_num(data, sector, block, byte + 3, 2) >> 3; + const uint8_t g_flag = pos_to_num(data, sector, block, byte + 3, 2) & 0b111; + const Money fare = money_parse(data, sector, block, byte + 5); + const uint16_t f_flag = pos_to_num(data, sector, block, byte + 5, 2) & 0x8001; + return (Transaction){date, gate, g_flag, fare, f_flag}; } -static Trip* trips_parse(const MfClassicData* data) { - /* Sectors 6 & 7 store the last 10 trips. Overall layout as follows: - - 0 1 2 3 4 5 6 7 8 9 A B C D E F - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x180 | trip0 | trip1 | crc1 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - ... ... ... ... - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1D0 | trip8 | trip9 | crc5 | - +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ - 0x1E0 | empty | crc6 | - +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ - - "empty" is all 0s. Trips are not sorted, rather, appear to get overwritten sequentially. (eg, sorted modulo array rotation) - */ - Trip* trips = malloc(sizeof(Trip) * CHARLIE_N_TRIP_HISTORY); +// ********************************************************** +// ******************* SECTOR PARSING *********************** +// ********************************************************** + +static uint32_t mfg_sector_parse(const MfClassicData* data) { + // Manufacturer data (Sector 0) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x000 | UID | rc | 88 04 00 C8 | uk | 00 20 00 00 00 | uk | + // +----.----.----.----+----+----.----.----.----+----+----.----.----.----.----+----+ + // 0x010 | 4E 0F 04 10 04 10 04 10 04 10 04 10 04 10 04 10 | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x020 | ... 00 00 ... | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // rc := "redundancy check" (lrc / bcc) + // uk := "unknown" - // Parse each trip field using some modular math magic to get the offsets: - // move from sector 6 -> 7 after the first 6 trips - // move a block within a given sector every 2 trips, reset every 3 blocks (as sector has changed) + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + const uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); + + return card_number; +} + +static CounterSector counter_sector_parse(const MfClassicData* data) { + // Trip/transaction counters (Sector 1) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x040 | 04 10 23 45 66 77 ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x050 | uses1 | uk | ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // 0x060 | uses2 | uk | ... 00 00 ... | + // +----.----+----+----.----.----.----.----.----.----.----.----.----.----.----.----+ + // + // uk := "unknown"; if nonzero, seems to only occupy the first 4 bits (ie, uk & 0xF0 == uk), + // with the remaining 4 zero + + // Card has two sectors (2 & 3) containing balance data, with two + // corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). + + // The *lower* of the two values *minus one* is the true use count, + // and corresponds to the active balance sector, + // (0x50 counter lower -> sector 2 active, 0x60 counter lower -> 3 active) + // per DEFCON31 researcher's findings + + const uint16_t n_uses1 = pos_to_num(data, 1, 1, 0, 2); + const uint16_t n_uses2 = pos_to_num(data, 1, 2, 0, 2); + + const bool is_sec2_active = n_uses1 <= n_uses2; + const uint8_t active_sector = is_sec2_active ? 2 : 3; + const uint16_t n_uses = (is_sec2_active ? n_uses1 : n_uses2) - 1; + + return (CounterSector){n_uses, active_sector}; +} + +static BalanceSector balance_sector_parse(const MfClassicData* data, uint8_t active_sector) { + // Balance & misc card info (Sector 2 or 3) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----+----.----.----+----.----+----.----.----+----.----+----.----+----+----.----+ + // 0x080 | 11 | date last | loc last| date issued | 65 00 | unknown | 00 | crc | 0x0C0 + // +----+----.----.----+----+----+----+----+----+----.----+----.----+----+----.----+ + // 0x090 | type |end validity| uk | balance | 00 | unknown | crc | 0x0D0 + // +----.----.----.----+----+----.----+----+----.----.----.----.----.----+----.----+ + // 0x0A0 | 20 ... 00 00 ... 04 | crc | 0x0E0 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // "Active" balance sector alternates between 2 and 3 + // Last trip/transaction info in balance sector ("date last" & "loc last") + // is also included in transaction log, hence don't bother to read here + // + // Inactive balance sector represent the transaction N-1 version + // (where active sector represents data from transaction N). + + const DateTime issued = date_parse(data, active_sector, 0, 6); + const DateTime end_validity = end_validity_parse(data, active_sector, 1, 1); + // Card type data stored in the first 10bits of block 1 + // (0x90 or 0xD0 depending on active sector) + // bitshift (2bytes = 16 bits) by 6bits for just first 10bits + const uint16_t type = pos_to_num(data, active_sector, 1, 0, 2) >> 6; + const Money bal = money_parse(data, active_sector, 1, 5); + + return (BalanceSector){bal, type, issued, end_validity}; +} + +static Pass* passes_parse(const MfClassicData* data) { + // Passes, speculative (Sectors 4 &/or 5) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ + // 0x100 | pass0/2? | 00 | pass1/3? | 00 | crc | 0x140 + // +----.----.----.----.----.----+----+----.----.----.----.----.----+----+----.----+ + // 0x110 | ... 00 00 ... | crc | 0x150 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // 0x120 | ... 00 ... 05 | crc | 0x160 + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // WIP. Read in all speculative passes into array + // 4 separate fields? active vs inactive sector for 2 passes? + // something else entirely? + + Pass* passes = malloc(sizeof(Pass) * CHARLIE_N_PASSES); + + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + passes[i] = pass_parse(data, 4 + (i / 2), 0, (i % 2) * 7); + } + + return passes; +} + +static Transaction* transactions_parse(const MfClassicData* data) { + // Transaction history (Sectors 6–7) + // + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x180 | transaction0 | transaction1 | crc | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // ... ... ... ... + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1D0 | transaction8 | transaction9 | crc | + // +----.----.----.----.----.----.----+----.----.----.----.----.----.----+----.----+ + // 0x1E0 | ... 00 00 ... | crc | + // +----.----.----.----.----.----.----.----.----.----.----.----.----.----+----.----+ + // + // Transactions are not sorted, rather, appear to get overwritten + // sequentially. (eg, sorted modulo array rotation) + + Transaction* transactions = malloc(sizeof(Transaction) * CHARLIE_N_TRANSACTION_HISTORY); + + // Parse each transaction field using some modular math magic to get the offsets: + // move from sector 6 -> 7 after the first 6 transactions + // move a block within a given sector every 2 transactions, reset every 3 blocks (as sector has changed) // alternate between a start byte of 0 and 7 with every iteration - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - trips[i] = trip_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + transactions[i] = transaction_parse(data, 6 + (i / 6), (i / 2) % 3, (i % 2) * 7); } // Iterate through the array to find the maximum (newest) date value int max_idx = 0; - for(int i = 1; i < CHARLIE_N_TRIP_HISTORY; i++) { - if(date_ge(trips[i].date, trips[max_idx].date)) { + for(int i = 1; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + if(dt_ge(transactions[i].date, transactions[max_idx].date)) { max_idx = i; } } @@ -793,78 +937,24 @@ static Trip* trips_parse(const MfClassicData* data) { // Sort by rotating for(int r = 0; r < (max_idx + 1); r++) { // Store the first element - Trip temp = trips[0]; + Transaction temp = transactions[0]; // Shift elements to the left - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY - 1; i++) { - trips[i] = trips[i + 1]; + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY - 1; i++) { + transactions[i] = transactions[i + 1]; } // Move the first element to the last - trips[CHARLIE_N_TRIP_HISTORY - 1] = temp; + transactions[CHARLIE_N_TRANSACTION_HISTORY - 1] = temp; } // Reverse order, such that newest is first, oldest last - for(int i = 0; i < CHARLIE_N_TRIP_HISTORY / 2; i++) { + for(int i = 0; i < CHARLIE_N_TRANSACTION_HISTORY / 2; i++) { // Swap elements at index i and size - i - 1 - Trip temp = trips[i]; - trips[i] = trips[CHARLIE_N_TRIP_HISTORY - i - 1]; - trips[CHARLIE_N_TRIP_HISTORY - i - 1] = temp; + Transaction temp = transactions[i]; + transactions[i] = transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1]; + transactions[CHARLIE_N_TRANSACTION_HISTORY - i - 1] = temp; } - return trips; -} - -static uint16_t n_uses(const MfClassicData* data, const enum CharlieActiveSector active_sector) { - /* First two bytes of applicable block (sector 1, block 1 or 2 depending on active_sector) - The *lower* of the two values *minus one* is the true use count, - per DEFCON31 researcher's findings - */ - return pos_to_num(data, 1, 1 + active_sector, 0, 2) - 1; -} - -static enum CharlieActiveSector get_active_sector(const MfClassicData* data) { - /* Card has two transaction sectors (2 & 3) containing balance data, with two - corresponding trip counters in 0x50:0x51 & 0x60:0x61 (sector 1, byte 0:1 of blocks 1 & 2). - - The *lower* count variable corresponds to the active sector - (0x5_ lower -> 2 active, 0x6_ lower -> 3 active) - - Sectors 2 & 3 are (largely) identical, save for trip data. - Card seems to alternate between the two, with active sector storing - the current balance & recent trip/transaction, & the inactive sector storing - the N-1 trip/transaction version of the same data. - - Here I check both the trip count and the stored transaction date, - for my own sanity, to confirm the active sector. - */ - - // active sector based on trip counters - const bool active_trip = n_uses(data, CHARLIE_ACTIVE_SECTOR_2) <= - n_uses(data, CHARLIE_ACTIVE_SECTOR_3); - - // active sector based on transaction date - DateTime ds2 = date_parse(data, 2, 0, 1); - DateTime ds3 = date_parse(data, 3, 0, 1); - const bool active_date = datetime_datetime_to_timestamp(&ds2) >= - datetime_datetime_to_timestamp(&ds3); - - // with all tested cards so far, this has been true - furi_assert(active_trip == active_date); - - return active_trip ? CHARLIE_ACTIVE_SECTOR_2 : CHARLIE_ACTIVE_SECTOR_3; -} - -static uint16_t type_parse(const MfClassicData* data) { - /* Card type data stored in the first 10bits of block 1 of sectors 2 & 3 (Block 9 & Block 13, from card start) - To my knowledge, card type should never change, so we can check either - without caring which is active. For my sanity, I check both, and assert equal. - */ - - // bitshift (2bytes = 16 bits) by 6bits for just first 10bits - const uint16_t type1 = pos_to_num(data, 2, 1, 0, 2) >> 6; - const uint16_t type2 = pos_to_num(data, 3, 1, 0, 2) >> 6; - furi_assert(type1 == type2); - - return type1; + return transactions; } /* @@ -897,18 +987,23 @@ static DateTime expiry(DateTime iss) { } return exp; -}*/ +} -static bool expired(DateTime expiry, DateTime last_trip) { +static bool expired(DateTime expiry, DateTime last_transaction) { // if a card has sat unused for >2 years, expired (verify this claim?) // else expired if current date > expiry date uint32_t ts_exp = datetime_datetime_to_timestamp(&expiry); - uint32_t ts_last = datetime_datetime_to_timestamp(&last_trip); + uint32_t ts_last = datetime_datetime_to_timestamp(&last_transaction); uint32_t ts_now = time_now(); return (ts_exp <= ts_now) | ((ts_now - ts_last) >= (2 * 365 * 24 * 60 * 60)); } +*/ + +// ********************************************************** +// ****************** STRING FORMATTING ********************* +// ********************************************************** void locale_format_dt_cat(FuriString* out, const DateTime* dt) { // helper to print datetimes @@ -935,36 +1030,84 @@ void type_format_cat(FuriString* out, uint16_t type) { furi_string_cat_str(out, s); } +void pass_format_cat(FuriString* out, Pass pass) { + furi_string_cat_printf(out, "\n-Pre: %b", pass.pre); + // type_format_cat(out, pass.type); + furi_string_cat_printf(out, "\n-Post: "); + type_format_cat(out, pass.post); + // locale_format_dt_cat(out, &pass.start); + furi_string_cat_printf(out, "\n-Date: "); + locale_format_dt_cat(out, &pass.date); +} + +void passes_format_cat(FuriString* out, Pass* passes) { + // only print passes if DEBUG on + if(!is_debug()) { + return; + } + + // only print if there is at least 1 valid pass to print + bool any_valid = false; + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + any_valid |= passes[i].valid; + } + if(!any_valid) { + return; + } + + furi_string_cat_printf(out, "\nPasses (DEBUG / WIP):"); + for(size_t i = 0; i < CHARLIE_N_PASSES; i++) { + if(passes[i].valid) { + furi_string_cat_printf(out, "\nPass %u", i + 1); + pass_format_cat(out, passes[i]); + furi_string_cat_printf(out, "\n"); + } + } +} + void money_format_cat(FuriString* out, Money money) { furi_string_cat_printf(out, "$%u.%02u", money.dollars, money.cents); } -void trip_format_cat(FuriString* out, Trip trip) { +void transaction_format_cat(FuriString* out, Transaction transaction) { const char* sep = " "; const char* sta; - locale_format_dt_cat(out, &trip.date); - furi_string_cat_printf(out, "\n%s", !!(trip.g_flag & 0x1) ? "-" : "+"); - money_format_cat(out, trip.fare); - if(!!(trip.g_flag & 0x1) && (trip.fare.dollars == FARE_BUS.dollars) && - (trip.fare.cents == FARE_BUS.cents)) { + locale_format_dt_cat(out, &transaction.date); + furi_string_cat_printf(out, "\n%s", !!(transaction.g_flag & 0x1) ? "-" : "+"); + money_format_cat(out, transaction.fare); + if(!!(transaction.g_flag & 0x1) && (transaction.fare.dollars == FARE_BUS.dollars) && + (transaction.fare.cents == FARE_BUS.cents)) { // if not a refill, and the fare amount is equal to bus fare (any better approach? flag bits for modality?) - // format for bus (gate ID on busses = posted bus #) - furi_string_cat_printf(out, "%sBus#%u", sep, trip.gate); - } else if(get_map_item(trip.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { + // format for bus — supposedly some correlation between gate ID & bus #, haven't investigated + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); + } else if(get_map_item(transaction.gate, charliecard_fare_gate_ids, kNumFareGateIds, &sta)) { // station found in fare gate ID map, append station name furi_string_cat_str(out, sep); furi_string_cat_str(out, sta); } else { // no found station in fare gate ID map & not a bus, just print ID w/o add'l info - furi_string_cat_printf(out, "%s%u", sep, trip.gate); + furi_string_cat_printf(out, "%s#%u", sep, transaction.gate); } // print flags for debugging purposes - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - furi_string_cat_printf(out, "%s%u%s%u", sep, trip.g_flag, sep, trip.f_flag); + if(is_debug()) { + furi_string_cat_printf(out, "%s%x%s%x", sep, transaction.g_flag, sep, transaction.f_flag); } } +void transactions_format_cat(FuriString* out, Transaction* transactions) { + furi_string_cat_printf(out, "\nTransactions:"); + for(size_t i = 0; i < CHARLIE_N_TRANSACTION_HISTORY; i++) { + furi_string_cat_printf(out, "\n"); + transaction_format_cat(out, transactions[i]); + furi_string_cat_printf(out, "\n"); + } +} + +// ********************************************************** +// **************** NFC PLUGIN BOILERPLATE ****************** +// ********************************************************** + static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); @@ -989,48 +1132,50 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) if(key_a != charliecard_1k_keys[verify_sector].a) break; if(key_b != charliecard_1k_keys[verify_sector].b) break; - // TODO: Verify add'l? - - const enum CharlieActiveSector active_sec_enum = get_active_sector(data); - const uint8_t active_sector = (active_sec_enum == CHARLIE_ACTIVE_SECTOR_2) ? 2 : 3; + // parse card data + const uint32_t card_number = mfg_sector_parse(data); + const CounterSector counter_sector = counter_sector_parse(data); + const BalanceSector balance_sector = + balance_sector_parse(data, counter_sector.active_balance_sector); + Pass* passes = passes_parse(data); + Transaction* transactions = transactions_parse(data); + // print/append card data furi_string_cat_printf(parsed_data, "\e#CharlieCard"); - - size_t uid_len = 0; - const uint8_t* uid = mf_classic_get_uid(data, &uid_len); - uint32_t card_number = bit_lib_bytes_to_num_be(uid, 4); furi_string_cat_printf(parsed_data, "\nSerial: 5-%lu", card_number); - Money bal = money_parse(data, active_sector, 1, 5); + // Type and balance 0 on some (Perq) cards + // (ie no "main" type / balance / end validity, + // essentially only pass & trip info) + // skip/change formatting for that case? furi_string_cat_printf(parsed_data, "\nBal: "); - money_format_cat(parsed_data, bal); + money_format_cat(parsed_data, balance_sector.balance); - const uint16_t type = type_parse(data); furi_string_cat_printf(parsed_data, "\nType: "); - type_format_cat(parsed_data, type); + type_format_cat(parsed_data, balance_sector.type); - const uint16_t n_trips = n_uses(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nTrip Count: %u", n_trips); + furi_string_cat_printf(parsed_data, "\nTrip Count: %u", counter_sector.n_uses); - const DateTime iss = date_parse(data, active_sector, 0, 6); furi_string_cat_printf(parsed_data, "\nIssued: "); - locale_format_dt_cat(parsed_data, &iss); + locale_format_dt_cat(parsed_data, &balance_sector.issued); + + if(!dt_eq(balance_sector.end_validity, CHARLIE_EPOCH) & + dt_ge(balance_sector.end_validity, balance_sector.issued)) { + // sometimes (seen on Perq cards) end validity field is all 0 + // When this is the case, calc'd end validity is equal to CHARLIE_EPOCH). + // Only print if not 0, & end validity after issuance date + furi_string_cat_printf(parsed_data, "\nExpiry: "); + locale_format_dt_cat(parsed_data, &balance_sector.end_validity); + } - const DateTime e_v = end_validity_parse(data, active_sec_enum); - furi_string_cat_printf(parsed_data, "\nExpiry: "); - locale_format_dt_cat(parsed_data, &e_v); + // const DateTime last = date_parse(data, active_sector, 0, 1); + // furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); - DateTime last = date_parse(data, active_sector, 0, 1); - furi_string_cat_printf(parsed_data, "\nExpired: %s", expired(e_v, last) ? "Yes" : "No"); + transactions_format_cat(parsed_data, transactions); + free(transactions); - Trip* trips = trips_parse(data); - furi_string_cat_printf(parsed_data, "\nTransactions:"); - for(size_t i = 0; i < CHARLIE_N_TRIP_HISTORY; i++) { - furi_string_cat_printf(parsed_data, "\n"); - trip_format_cat(parsed_data, trips[i]); - furi_string_cat_printf(parsed_data, "\n"); - } - free(trips); + passes_format_cat(parsed_data, passes); + free(passes); parsed = true; } while(false); @@ -1038,6 +1183,78 @@ static bool charliecard_parse(const NfcDevice* device, FuriString* parsed_data) return parsed; } +static bool charliecard_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 1; + const uint8_t verify_block = mf_classic_get_first_block_num_of_sector(verify_sector) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {0}; + bit_lib_num_to_bytes_be( + charliecard_1k_keys[verify_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, verify_block, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", verify_block, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool charliecard_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + charliecard_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + charliecard_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin charliecard_plugin = { .protocol = NfcProtocolMfClassic, diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index 6123c00292..3e97d0a9a7 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -82,7 +82,7 @@ void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t sta bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); - FURI_LOG_I(TAG, "Transport departament: %x", transport_departament); + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); if(layout_type == 0xE) { @@ -91,7 +91,7 @@ bool parse_transport_block(const MfClassicBlock* block, FuriString* result) { layout_type = bit_lib_get_bits_16(block->data, 52, 14); } - FURI_LOG_I(TAG, "Layout type %x", layout_type); + FURI_LOG_D(TAG, "Layout type %x", layout_type); uint16_t card_view = 0; uint16_t card_type = 0; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index e522cd059a..2457fb1561 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -33,6 +33,8 @@ ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) +ADD_SCENE(nfc, felica_key_input, FelicaKeyInput) +ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_key_input.c b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c new file mode 100644 index 0000000000..b04f12dae8 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c @@ -0,0 +1,46 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_key_input_byte_input_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_felica_key_input_on_enter(void* context) { + NfcApp* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_felica_key_input_byte_input_callback, + NULL, + nfc, + nfc->felica_auth->card_key.data, + FELICA_DATA_BLOCK_SIZE); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_felica_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_key_input_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c new file mode 100644 index 0000000000..15b61dfa51 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_unlock_warn_dialog_callback(DialogExResult result, void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_felica_unlock_warn_on_enter(void* context) { + NfcApp* nfc = context; + + const char* message = "Risky Action!"; + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback(dialog_ex, nfc_scene_felica_unlock_warn_dialog_callback); + + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); + + FuriString* str = furi_string_alloc(); + furi_string_cat_printf(str, "Unlock with key: "); + for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) + furi_string_cat_printf(str, "%02X ", nfc->felica_auth->card_key.data[i]); + furi_string_cat_printf(str, "?"); + + nfc_text_store_set(nfc, furi_string_get_cstr(str)); + furi_string_free(str); + + dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 12, AlignLeft, AlignTop); + + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Unlock"); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } else if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_unlock_warn_on_exit(void* context) { + NfcApp* nfc = context; + + dialog_ex_reset(nfc->dialog_ex); + nfc_text_store_clear(nfc); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c index 76834e3f4f..3e111c723c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) { for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); furi_string_printf( - label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]); submenu_add_item( submenu, furi_string_get_cstr(label), diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 308f6dbb33..4343040244 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -60,9 +60,6 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e subghz_frequency_analyzer_get_frequency_to_save(subghz->subghz_frequency_analyzer); if(frequency > 0) { subghz->last_settings->frequency = frequency; -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif // Disable Hopping before opening the receiver scene! if(subghz->last_settings->enable_hopping) { subghz->last_settings->enable_hopping = false; @@ -73,9 +70,6 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e return true; } else if(event.event == SubGhzCustomEventViewFreqAnalOkLong) { // Don't need to save, we already saved on short event -#ifdef FURI_DEBUG - FURI_LOG_W(TAG, "Goto next scene!"); -#endif //scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, 10); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver); return true; diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index f293bc86c7..270d999c0c 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -20,12 +20,6 @@ const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { "ON", }; -#define EXT_MOD_POWER_AMP_COUNT 2 -const char* const ext_mod_power_amp_text[EXT_MOD_POWER_AMP_COUNT] = { - "OFF", - "ON", -}; - #define DEBUG_P_COUNT 2 const char* const debug_pin_text[DEBUG_P_COUNT] = { "OFF", @@ -104,27 +98,6 @@ static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { furi_hal_subghz_set_rolling_counter_mult(debug_counter_val[index]); } -static void subghz_scene_reciever_config_set_ext_mod_power_amp_text(VariableItem* item) { - SubGhz* subghz = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, ext_mod_power_amp_text[index]); - - subghz->last_settings->external_module_power_amp = index == 1; - - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(subghz->last_settings->external_module_power_amp); - - subghz_last_settings_save(subghz->last_settings); - - // reinit external device - const SubGhzRadioDeviceType current = subghz_txrx_radio_device_get(subghz->txrx); - if(current != SubGhzRadioDeviceTypeInternal) { - subghz_txrx_radio_device_set(subghz->txrx, SubGhzRadioDeviceTypeInternal); - subghz_txrx_radio_device_set(subghz->txrx, current); - } -} - static void subghz_scene_receiver_config_set_gps(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -194,16 +167,6 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, radio_device_text[value_index]); - item = variable_item_list_add( - variable_item_list, - "Ext Power Amp", - EXT_MOD_POWER_AMP_COUNT, - subghz_scene_reciever_config_set_ext_mod_power_amp_text, - subghz); - value_index = subghz->last_settings->external_module_power_amp ? 1 : 0; - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, ext_mod_power_amp_text[value_index]); - item = variable_item_list_add( variable_item_list, "GPS Baudrate", diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index ca12689178..874c3b3ed5 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -104,14 +104,13 @@ void subghz_scene_read_raw_on_enter(void* context) { if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); -#if SUBGHZ_LAST_SETTING_SAVE_PRESET + if(furi_string_empty(file_name)) { subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); } -#endif } subghz_scene_read_raw_update_statusbar(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index cd300e7c13..32ad1d2c95 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -250,12 +250,8 @@ void subghz_scene_receiver_on_enter(void* context) { FuriString* item_time = furi_string_alloc(); if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->filter = subghz->last_settings->filter; subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index ce0d82ee6b..7986cbe661 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -410,14 +410,11 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock); } else if(index == SubGhzSettingIndexResetToDefault) { // Reset all values to default state! -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY, SUBGHZ_LAST_SETTING_DEFAULT_PRESET); -#else - subghz_txrx_set_default_preset(subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY); -#endif + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); const char* preset_name = furi_string_get_cstr(preset.name); @@ -449,9 +446,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, variable_item_list_set_selected_item(subghz->variable_item_list, default_index); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif + subghz_last_settings_save(subghz->last_settings); view_dispatcher_send_custom_event( @@ -745,9 +740,7 @@ void subghz_scene_receiver_config_on_exit(void* context) { variable_item_list_set_selected_item(subghz->variable_item_list, 0); variable_item_list_reset(subghz->variable_item_list); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif + subghz_last_settings_save(subghz->last_settings); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); diff --git a/applications/main/subghz/scenes/subghz_scene_set_cnt.c b/applications/main/subghz/scenes/subghz_scene_set_cnt.c index 4952c2b14e..b202d6d91e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_cnt.c +++ b/applications/main/subghz/scenes/subghz_scene_set_cnt.c @@ -18,7 +18,7 @@ void subghz_scene_set_cnt_on_enter(void* context) { switch(state) { case SetTypeBFTClone: - byte_input_set_header_text(byte_input, "Enter COUNTER in Hex"); + byte_input_set_header_text(byte_input, "Enter COUNTER in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_cnt_byte_input_callback, @@ -29,7 +29,7 @@ void subghz_scene_set_cnt_on_enter(void* context) { break; case SetTypeFaacSLH_Manual_433: case SetTypeFaacSLH_Manual_868: - byte_input_set_header_text(byte_input, "Enter COUNTER in Hex 20 bits"); + byte_input_set_header_text(byte_input, "Enter COUNTER in hex 20 bits"); byte_input_set_result_callback( byte_input, subghz_scene_set_cnt_byte_input_callback, diff --git a/applications/main/subghz/scenes/subghz_scene_set_fix.c b/applications/main/subghz/scenes/subghz_scene_set_fix.c index b58f39ddd0..7564fb24d7 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_fix.c +++ b/applications/main/subghz/scenes/subghz_scene_set_fix.c @@ -13,7 +13,7 @@ void subghz_scene_set_fix_on_enter(void* context) { // Setup view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter FIX in Hex"); + byte_input_set_header_text(byte_input, "Enter FIX in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_fix_byte_input_callback, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index a22daf9196..c8301745fa 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -14,7 +14,7 @@ void subghz_scene_set_seed_on_enter(void* context) { // Setup view ByteInput* byte_input = subghz->byte_input; - byte_input_set_header_text(byte_input, "Enter SEED in Hex"); + byte_input_set_header_text(byte_input, "Enter SEED in hex"); byte_input_set_result_callback( byte_input, subghz_scene_set_seed_byte_input_callback, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 90b30f0768..b88e449b15 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -445,8 +445,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenKeeloq, .mod = "FM476", .freq = 434420000, - .keeloq.serial = key & 0x0FFFFFFF, - .keeloq.btn = 0x04, + .keeloq.serial = (key & 0x0000FFFF) | 0x07150000, + .keeloq.btn = 0x02, .keeloq.cnt = 0x03, .keeloq.manuf = "Sommer(fsk476)"}; break; @@ -455,8 +455,8 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenKeeloq, .mod = "FM476", .freq = 868800000, - .keeloq.serial = key & 0x0FFFFFFF, - .keeloq.btn = 0x04, + .keeloq.serial = (key & 0x0000FFFF) | 0x07150000, + .keeloq.btn = 0x02, .keeloq.cnt = 0x03, .keeloq.manuf = "Sommer(fsk476)"}; break; diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index a884759dd6..7d21bb2cf3 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -207,16 +207,9 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->last_settings = subghz_last_settings_alloc(); size_t preset_count = subghz_setting_get_preset_count(setting); subghz_last_settings_load(subghz->last_settings, preset_count); -#ifdef FURI_DEBUG - subghz_last_settings_log(subghz->last_settings); -#endif if(!alloc_for_tx_only) { -#if SUBGHZ_LAST_SETTING_SAVE_PRESET subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); -#else - subghz_txrx_set_default_preset(subghz->txrx, subghz->last_settings->frequency); -#endif subghz->history = subghz_history_alloc(); } diff --git a/applications/main/subghz/subghz_extended_freq.c b/applications/main/subghz/subghz_extended_freq.c index d7f41a25bf..eebcc5856c 100644 --- a/applications/main/subghz/subghz_extended_freq.c +++ b/applications/main/subghz/subghz_extended_freq.c @@ -26,8 +26,5 @@ void subghz_extended_freq() { SubGhzLastSettings* last_settings = subghz_last_settings_alloc(); subghz_last_settings_load(last_settings, 0); - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(last_settings->external_module_power_amp); - subghz_last_settings_free(last_settings); } diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 0ab6213067..4d1e44dc6e 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -4,26 +4,24 @@ #define TAG "SubGhzLastSettings" #define SUBGHZ_LAST_SETTING_FILE_TYPE "Flipper SubGhz Last Setting File" -#define SUBGHZ_LAST_SETTING_FILE_VERSION 1 +#define SUBGHZ_LAST_SETTING_FILE_VERSION 3 #define SUBGHZ_LAST_SETTINGS_PATH EXT_PATH("subghz/assets/last_subghz.settings") #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY "Frequency" #define SUBGHZ_LAST_SETTING_FIELD_PRESET "Preset" // AKA Modulation #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL "FeedbackLevel" #define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED "External" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER "ExtPower" -#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "TimestampNames" -#define SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP "ExtPowerAmp" -#define SUBGHZ_LAST_SETTING_FIELD_GPS "Gps" +#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "ProtocolNames" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping" -#define SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES "RemoveDuplicates" #define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter" #define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter" #define SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD "RSSI" +#define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" + +#define SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE "GpsBaudrate" +#define SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES "RemoveDuplicates" #define SUBGHZ_LAST_SETTING_FIELD_REPEATER "Repeater" #define SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND "Sound" -#define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" #define SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE "Autosave" SubGhzLastSettings* subghz_last_settings_alloc(void) { @@ -39,202 +37,145 @@ void subghz_last_settings_free(SubGhzLastSettings* instance) { void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count) { furi_assert(instance); + // Default values (all others set to 0, if read from file fails these are used) + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + instance->frequency_analyzer_feedback_level = + SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; + instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; + // See bin_raw_value in scenes/subghz_scene_receiver_config.c + instance->filter = SubGhzProtocolFlag_Decodable; + instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; + Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); - uint32_t temp_frequency = 0; - uint32_t temp_frequency_analyzer_feedback_level = 0; - float temp_frequency_analyzer_trigger = 0; - bool temp_external_module_enabled = false; - bool temp_external_module_power_5v_disable = false; - bool temp_external_module_power_amp = false; - bool temp_protocol_file_names = false; - bool temp_enable_hopping = false; - bool temp_enable_sound = false; - uint32_t temp_repeater_state; - bool temp_remove_duplicates = false; - bool temp_delete_old_sig = false; - bool temp_autosave = false; - uint32_t temp_ignore_filter = 0; - uint32_t temp_filter = 0; - float temp_rssi = 0; - uint32_t temp_preset = 0; - - bool preset_was_read = false; - bool rssi_was_read = false; - bool filter_was_read = false; - bool ignore_filter_was_read = false; - bool remove_duplicates_was_read = false; - bool frequency_analyzer_feedback_level_was_read = false; - bool frequency_analyzer_trigger_was_read = false; - bool repeater_was_read = false; - bool enable_sound_was_read = false; + FuriString* temp_str = furi_string_alloc(); + uint32_t config_version = 0; - uint32_t temp_gps_baudrate = 0; - - if(FSE_OK == storage_sd_status(storage) && SUBGHZ_LAST_SETTINGS_PATH && + if(FSE_OK == storage_sd_status(storage) && flipper_format_file_open_existing(fff_data_file, SUBGHZ_LAST_SETTINGS_PATH)) { - preset_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, (uint32_t*)&temp_preset, 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, (uint32_t*)&temp_frequency, 1); - frequency_analyzer_feedback_level_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, - (uint32_t*)&temp_frequency_analyzer_feedback_level, - 1); - frequency_analyzer_trigger_was_read = flipper_format_read_float( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, - (float*)&temp_frequency_analyzer_trigger, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - (bool*)&temp_external_module_enabled, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - (bool*)&temp_external_module_power_5v_disable, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, - (bool*)&temp_protocol_file_names, - 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - (bool*)&temp_external_module_power_amp, - 1); - flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_GPS, (uint32_t*)&temp_gps_baudrate, 1); - flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, - (bool*)&temp_enable_hopping, - 1); - rssi_was_read = flipper_format_read_float( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, (float*)&temp_rssi, 1); - remove_duplicates_was_read = flipper_format_read_bool( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, - (bool*)&temp_remove_duplicates, - 1); - ignore_filter_was_read = flipper_format_read_uint32( - fff_data_file, - SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, - (uint32_t*)&temp_ignore_filter, - 1); - filter_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, (uint32_t*)&temp_filter, 1); - repeater_was_read = flipper_format_read_uint32( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_REPEATER, (uint32_t*)&temp_repeater_state, 1); - enable_sound_was_read = flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, (bool*)&temp_enable_sound, 1); - flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, (bool*)&temp_delete_old_sig, 1); - flipper_format_read_bool( - fff_data_file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, (bool*)&temp_autosave, 1); - + do { + if(!flipper_format_read_header(fff_data_file, temp_str, &config_version)) break; + if((strcmp(furi_string_get_cstr(temp_str), SUBGHZ_LAST_SETTING_FILE_TYPE) != 0) || + (config_version != SUBGHZ_LAST_SETTING_FILE_VERSION)) { + break; + } + + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, + &instance->frequency_analyzer_feedback_level, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, + &instance->frequency_analyzer_trigger, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, + &instance->protocol_file_names, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, + &instance->enable_hopping, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, + &instance->ignore_filter, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_float( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, + &instance->delete_old_signals, + 1)) { + flipper_format_rewind(fff_data_file); + } + + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE, + &instance->gps_baudrate, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, + &instance->remove_duplicates, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_uint32( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_REPEATER, + &instance->repeater_state, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, + &instance->enable_sound, + 1)) { + flipper_format_rewind(fff_data_file); + } + if(!flipper_format_read_bool( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, &instance->autosave, 1)) { + flipper_format_rewind(fff_data_file); + } + } while(0); } else { FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH); } - if(temp_frequency == 0 || !furi_hal_subghz_is_tx_allowed(temp_frequency)) { - FURI_LOG_W(TAG, "Last used frequency not found or can't be used!"); - - instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - instance->frequency_analyzer_feedback_level = - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - instance->frequency_analyzer_trigger = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - instance->external_module_enabled = false; - instance->protocol_file_names = false; - instance->external_module_power_amp = false; - instance->gps_baudrate = 0; - instance->enable_hopping = false; - instance->remove_duplicates = false; - instance->repeater_state = 0; - instance->enable_sound = 0; - instance->delete_old_signals = false; - instance->autosave = false; - instance->ignore_filter = 0x00; - // See bin_raw_value in applications/main/subghz/scenes/subghz_scene_receiver_config.c - instance->filter = SubGhzProtocolFlag_Decodable; - instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; - } else { - instance->frequency = temp_frequency; - instance->frequency_analyzer_feedback_level = - frequency_analyzer_feedback_level_was_read ? - temp_frequency_analyzer_feedback_level : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL; - - instance->frequency_analyzer_trigger = frequency_analyzer_trigger_was_read ? - temp_frequency_analyzer_trigger : - SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER; - - if(!preset_was_read) { - FURI_LOG_W(TAG, "Preset was not read. Set default"); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else if(temp_preset > (uint32_t)preset_count - 1) { - FURI_LOG_W( - TAG, - "Last used preset out of range. Preset to set: %ld, Max index: %ld. Set default", - temp_preset, - (uint32_t)preset_count - 1); - instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; - } else { - instance->preset_index = temp_preset; - } - instance->external_module_enabled = temp_external_module_enabled; - - instance->external_module_power_5v_disable = temp_external_module_power_5v_disable; - - instance->protocol_file_names = temp_protocol_file_names; - - instance->delete_old_signals = temp_delete_old_sig; - - instance->autosave = temp_autosave; - - // External power amp CC1101 - instance->external_module_power_amp = temp_external_module_power_amp; - - instance->rssi = rssi_was_read ? temp_rssi : SUBGHZ_RAW_THRESHOLD_MIN; - instance->enable_hopping = temp_enable_hopping; - instance->repeater_state = repeater_was_read ? temp_repeater_state : 0; - instance->enable_sound = enable_sound_was_read ? temp_enable_sound : false; - instance->remove_duplicates = remove_duplicates_was_read ? temp_remove_duplicates : false; - instance->ignore_filter = ignore_filter_was_read ? temp_ignore_filter : 0x00; -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW - instance->filter = filter_was_read ? temp_filter : SubGhzProtocolFlag_Decodable; -#else - if(filter_was_read) { - instance->filter = temp_filter != SubGhzProtocolFlag_Decodable ? - SubGhzProtocolFlag_Decodable : - temp_filter; - } else { - instance->filter = SubGhzProtocolFlag_Decodable; - } -#endif - // Set globally in furi hal - furi_hal_subghz_set_ext_power_amp(instance->external_module_power_amp); - - instance->gps_baudrate = temp_gps_baudrate; - } + furi_string_free(temp_str); flipper_format_file_close(fff_data_file); flipper_format_free(fff_data_file); furi_record_close(RECORD_STORAGE); + + if(instance->frequency == 0 || !furi_hal_subghz_is_tx_allowed(instance->frequency)) { + instance->frequency = SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY; + } + + if(instance->preset_index > (uint32_t)preset_count - 1) { + instance->preset_index = SUBGHZ_LAST_SETTING_DEFAULT_PRESET; + } } bool subghz_last_settings_save(SubGhzLastSettings* instance) { furi_assert(instance); -#if SUBGHZ_LAST_SETTING_SAVE_BIN_RAW != true - instance->filter = SubGhzProtocolFlag_Decodable; -#endif bool saved = false; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* file = flipper_format_file_alloc(storage); @@ -251,96 +192,76 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { if(!flipper_format_write_header_cstr( file, SUBGHZ_LAST_SETTING_FILE_TYPE, SUBGHZ_LAST_SETTING_FILE_VERSION)) break; - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY, &instance->frequency, 1)) { + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_PRESET, &instance->preset_index, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_FEEDBACK_LEVEL, &instance->frequency_analyzer_feedback_level, 1)) { break; } - if(!flipper_format_insert_or_update_float( + if(!flipper_format_write_float( file, SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER, &instance->frequency_analyzer_trigger, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_ENABLED, - &instance->external_module_enabled, + SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, + &instance->protocol_file_names, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER, - &instance->external_module_power_5v_disable, - 1)) { + if(!flipper_format_write_bool( + file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES, - &instance->protocol_file_names, - 1)) { + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, &instance->ignore_filter, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, - SUBGHZ_LAST_SETTING_FIELD_EXTERNAL_MODULE_POWER_AMP, - &instance->external_module_power_amp, - 1)) { + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_GPS, &instance->gps_baudrate, 1)) { + if(!flipper_format_write_float( + file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) { + if(!flipper_format_write_bool( + file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { break; } - if(!flipper_format_insert_or_update_float( - file, SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD, &instance->rssi, 1)) { + + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_GPS_BAUDRATE, &instance->gps_baudrate, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_REMOVE_DUPLICATES, &instance->remove_duplicates, 1)) { break; } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER, &instance->ignore_filter, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_FILTER, &instance->filter, 1)) { - break; - } - if(!flipper_format_insert_or_update_uint32( + if(!flipper_format_write_uint32( file, SUBGHZ_LAST_SETTING_FIELD_REPEATER, &instance->repeater_state, 1)) { break; } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND, &instance->enable_sound, 1)) { break; } - if(!flipper_format_insert_or_update_bool( - file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { - break; - } - if(!flipper_format_insert_or_update_bool( + if(!flipper_format_write_bool( file, SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE, &instance->autosave, 1)) { break; } @@ -357,54 +278,3 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { return saved; } - -const char* LOG_ON = "ON"; -const char* LOG_OFF = "OFF"; - -static inline const char* - subghz_last_settings_log_filter_get_index(uint32_t filter, uint32_t flag) { - return READ_BIT(filter, flag) > 0 ? LOG_ON : LOG_OFF; -} - -static inline const char* bool_to_char(bool value) { - return value ? LOG_ON : LOG_OFF; -} - -void subghz_last_settings_log(SubGhzLastSettings* instance) { - furi_assert(instance); - - FURI_LOG_I( - TAG, - "Frequency: %03ld.%02ld, FeedbackLevel: %ld, FATrigger: %.2f, External: %s, ExtPower: %s, TimestampNames: %s, ExtPowerAmp: %s,\n" - "GPSBaudrate: %ld, Hopping: %s,\nPreset: %ld, RSSI: %.2f, " - "BinRAW: %s, Repeater: %lu, Duplicates: %s, Autosave: %s, Starline: %s, Cars: %s, Magellan: %s, NiceFloR-S: %s, Weather: %s, TPMS: %s, Sound: %s", - instance->frequency / 1000000 % 1000, - instance->frequency / 10000 % 100, - instance->frequency_analyzer_feedback_level, - (double)instance->frequency_analyzer_trigger, - bool_to_char(instance->external_module_enabled), - bool_to_char(instance->external_module_power_5v_disable), - bool_to_char(instance->protocol_file_names), - bool_to_char(instance->external_module_power_amp), - instance->gps_baudrate, - bool_to_char(instance->enable_hopping), - instance->preset_index, - (double)instance->rssi, - subghz_last_settings_log_filter_get_index(instance->filter, SubGhzProtocolFlag_BinRAW), - instance->repeater_state, - bool_to_char(instance->remove_duplicates), - bool_to_char(instance->autosave), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_StarLine), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_AutoAlarms), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_Magellan), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_NiceFlorS), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_Weather), - subghz_last_settings_log_filter_get_index( - instance->ignore_filter, SubGhzProtocolFilter_TPMS), - bool_to_char(instance->enable_sound)); -} diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 3c03c0f116..91dfbef3a8 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -7,8 +7,6 @@ #include #define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER (-93.0f) -#define SUBGHZ_LAST_SETTING_SAVE_BIN_RAW true -#define SUBGHZ_LAST_SETTING_SAVE_PRESET true // 1 = "AM650" // "AM270", "AM650", "FM238", "FM476", #define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1 @@ -20,21 +18,17 @@ typedef struct { uint32_t preset_index; // AKA Modulation uint32_t frequency_analyzer_feedback_level; float frequency_analyzer_trigger; - // TODO not using but saved so as not to change the version - bool external_module_enabled; - bool external_module_power_5v_disable; - bool external_module_power_amp; - // saved so as not to change the version bool protocol_file_names; - uint32_t gps_baudrate; bool enable_hopping; - uint32_t repeater_state; - bool enable_sound; - bool remove_duplicates; uint32_t ignore_filter; uint32_t filter; float rssi; bool delete_old_signals; + + uint32_t gps_baudrate; + bool remove_duplicates; + uint32_t repeater_state; + bool enable_sound; bool autosave; } SubGhzLastSettings; @@ -45,5 +39,3 @@ void subghz_last_settings_free(SubGhzLastSettings* instance); void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count); bool subghz_last_settings_save(SubGhzLastSettings* instance); - -void subghz_last_settings_log(SubGhzLastSettings* instance); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index bc472f2339..f16a38d3e5 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -228,9 +228,7 @@ uint32_t subghz_frequency_find_correct(uint32_t input) { uint32_t prev_freq = 0; uint32_t current = 0; uint32_t result = 0; -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "input: %ld", input); -#endif + for(size_t i = 0; i < sizeof(subghz_frequency_list); i++) { current = subghz_frequency_list[i]; if(current == input) { @@ -281,7 +279,7 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { break; } subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level); - FURI_LOG_I(TAG, "trigger = %.1f", (double)trigger_level); + FURI_LOG_D(TAG, "trigger = %.1f", (double)trigger_level); need_redraw = true; } else if(event->type == InputTypePress && event->key == InputKeyUp) { if(instance->feedback_level == 0) { @@ -289,9 +287,7 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } else { instance->feedback_level--; } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "feedback_level = %d", instance->feedback_level); -#endif + need_redraw = true; } else if( ((event->type == InputTypePress) || (event->type == InputTypeRepeat)) && @@ -324,13 +320,6 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) { -#ifdef FURI_DEBUG - FURI_LOG_D( - TAG, - "frequency_to_save: %ld, candidate: %ld", - model->frequency_to_save, - frequency_candidate); -#endif model->frequency_to_save = frequency_candidate; updated = true; } @@ -372,24 +361,12 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { }, true); -#ifdef FURI_DEBUG - FURI_LOG_I( - TAG, - "updated: %d, long: %d, type: %d", - updated, - (event->type == InputTypeLong), - event->type); -#endif - if(updated) { instance->callback(SubGhzCustomEventViewFreqAnalOkShort, instance->context); } // First device receive short, then when user release button we get long if(event->type == InputTypeLong && frequency_to_save > 0) { -#ifdef FURI_DEBUG - FURI_LOG_I(TAG, "Long press!"); -#endif // Stop worker if(subghz_frequency_analyzer_worker_is_running(instance->worker)) { subghz_frequency_analyzer_worker_stop(instance->worker); diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 9805e7f104..1a91e2622a 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -435,8 +435,34 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Pool free: %zu\r\n", memmgr_pool_get_free()); - printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); + printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); + printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); +} + +typedef struct { + void* addr; + size_t size; +} FreeBlockInfo; + +#define FREE_BLOCK_INFO_MAX 128 + +typedef struct { + FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; + size_t free_blocks_count; +} FreeBlockContext; + +static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { + FreeBlockContext* free_blocks = (FreeBlockContext*)context; + if(!used) { + if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { + free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; + free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; + free_blocks->free_blocks_count++; + } else { + return false; + } + } + return true; } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { @@ -444,7 +470,23 @@ void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - memmgr_heap_printf_free_blocks(); + FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); + free_blocks->free_blocks_count = 0; + + memmgr_heap_walk_blocks(free_block_walker, free_blocks); + + for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { + printf( + "A %p S %zu\r\n", + (void*)free_blocks->free_blocks[i].addr, + free_blocks->free_blocks[i].size); + } + + if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { + printf("... and more\r\n"); + } + + free(free_blocks); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index a042dc56b0..38914f43ae 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -34,10 +34,8 @@ static void desktop_loader_callback(const void* message, void* context) { const LoaderEvent* event = message; if(event->type == LoaderEventTypeApplicationBeforeLoad) { - desktop->animation_lock = api_lock_alloc_locked(); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); - api_lock_wait_unlock_and_free(desktop->animation_lock); - desktop->animation_lock = NULL; + furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); } else if( event->type == LoaderEventTypeApplicationLoadFailed || event->type == LoaderEventTypeApplicationStopped) { @@ -125,7 +123,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_unload_and_stall_animation(desktop->animation_manager); } desktop_auto_lock_inhibit(desktop); - api_lock_unlock(desktop->animation_lock); + furi_semaphore_release(desktop->animation_semaphore); return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); @@ -282,6 +280,7 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { Desktop* desktop_alloc(void) { Desktop* desktop = malloc(sizeof(Desktop)); + desktop->animation_semaphore = furi_semaphore_alloc(1, 0); desktop->animation_manager = animation_manager_alloc(); desktop->gui = furi_record_open(RECORD_GUI); desktop->scene_thread = furi_thread_alloc(); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index c06ba40a25..1c1598c796 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -20,7 +20,6 @@ #include #include -#include #define STATUS_BAR_Y_SHIFT 13 @@ -81,7 +80,7 @@ struct Desktop { bool in_transition : 1; - FuriApiLock animation_lock; + FuriSemaphore* animation_semaphore; Keybinds keybinds; diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 0bc08cd27c..a94d627cef 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -53,7 +53,6 @@ void view_dispatcher_set_navigation_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherNavigationEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->navigation_event_callback = callback; } @@ -61,7 +60,6 @@ void view_dispatcher_set_custom_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherCustomEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->custom_event_callback = callback; } @@ -70,7 +68,6 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 03de06b305..446a6b10d7 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -24,14 +24,14 @@ static const char* loader_find_external_application_by_name(const char* app_name, FlipperApplicationFlag* flags) { for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) { - *flags = FLIPPER_EXTERNAL_APPS[i].flags; + if(flags) *flags = FLIPPER_EXTERNAL_APPS[i].flags; return FLIPPER_EXTERNAL_APPS[i].path; } } for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { if(strcmp(FLIPPER_SETTINGS_APPS[i].name, app_name) == 0) { - *flags = FLIPPER_SETTINGS_APPS[i].flags; + if(flags) *flags = FLIPPER_SETTINGS_APPS[i].flags; return FLIPPER_SETTINGS_APPS[i].path; } } @@ -61,10 +61,24 @@ LoaderStatus return result.value; } -static void loader_show_gui_error(LoaderStatus status, FuriString* error_message) { - // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu - // so i prefer to not show LoaderStatusErrorAppStarted error message for now - if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { +static void + loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) { + if(status == LoaderStatusErrorUnknownApp && + loader_find_external_application_by_name(name, NULL) != NULL) { + // Special case for external apps + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + dialog_message_set_buttons(message, NULL, NULL, NULL); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); + dialog_message_set_text( + message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) { + // TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu + // so i prefer to not show LoaderStatusErrorAppStarted error message for now DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop); @@ -90,7 +104,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_start(loader, name, args, error_message); - loader_show_gui_error(status, error_message); + loader_show_gui_error(status, name, error_message); furi_string_free(error_message); return status; } @@ -99,11 +113,11 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons furi_check(loader); furi_check(name); - LoaderMessage message; - - message.type = LoaderMessageTypeStartByNameDetachedWithGuiError; - message.start.name = name ? strdup(name) : NULL; - message.start.args = args ? strdup(args) : NULL; + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = strdup(name), + .start.args = args ? strdup(args) : NULL, + }; furi_message_queue_put(loader->queue, &message, FuriWaitForever); } @@ -196,11 +210,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con Loader* loader = context; - if(thread_state == FuriThreadStateRunning) { - LoaderEvent event; - event.type = LoaderEventTypeApplicationStarted; - furi_pubsub_publish(loader->pubsub, &event); - } else if(thread_state == FuriThreadStateStopped) { + if(thread_state == FuriThreadStateStopped) { LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); @@ -742,7 +752,7 @@ int32_t loader_srv(void* p) { FuriString* error_message = furi_string_alloc(); LoaderStatus status = loader_do_start_by_name( loader, message.start.name, message.start.args, error_message); - loader_show_gui_error(status, error_message); + loader_show_gui_error(status, message.start.name, error_message); if(message.start.name) free((void*)message.start.name); if(message.start.args) free((void*)message.start.args); furi_string_free(error_message); diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index 66669bc745..0223cfffd3 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -20,7 +20,6 @@ typedef enum { typedef enum { LoaderEventTypeApplicationBeforeLoad, LoaderEventTypeApplicationLoadFailed, - LoaderEventTypeApplicationStarted, LoaderEventTypeApplicationStopped } LoaderEventType; @@ -34,7 +33,7 @@ typedef struct { * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); @@ -44,19 +43,19 @@ LoaderStatus * @param[in] instance loader instance * @param[in] name application name or id * @param[in] args application arguments - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); /** * @brief Start application detached with GUI error message * @param[in] instance loader instance - * @param[in] name application name + * @param[in] name application name or id * @param[in] args application arguments */ void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); -/** +/** * @brief Lock application start * @param[in] instance loader instance * @return true on success diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index d8b4d235c7..e6c93b316b 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -33,8 +33,8 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, - LoaderMessageTypeStartByNameDetachedWithGuiError, + LoaderMessageTypeShowSettings, } LoaderMessageType; diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 7badb7103a..39724910c8 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -300,9 +300,11 @@ static void power_loader_callback(const void* message, void* context) { Power* power = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { power_auto_shutdown_inhibit(power); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { power_auto_shutdown_arm(power); } } diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index f2997ffcf4..b51c0df6c4 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -43,10 +43,12 @@ Storage* storage_app_alloc(void) { } #ifndef FURI_RAM_EXEC - storage_mnt_init(&app->storage[ST_MNT]); storage_int_init(&app->storage[ST_INT]); #endif storage_ext_init(&app->storage[ST_EXT]); +#ifndef FURI_RAM_EXEC + storage_mnt_init(&app->storage[ST_MNT]); +#endif // sd icon gui app->sd_gui.enabled = false; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index 41da6c3f48..727af765fe 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -156,3 +156,9 @@ size_t storage_open_files_count(StorageData* storage) { size_t count = StorageFileList_size(storage->files); return count; } + +const char* storage_file_get_path(File* file, StorageData* storage) { + StorageFile* storage_file_ref = storage_get_file(file, storage); + if(!storage_file_ref) return ""; + return furi_string_get_cstr(storage_file_ref->path); +} \ No newline at end of file diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index fbc08ebbf9..9a19ae815a 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -35,6 +35,7 @@ void storage_file_init(StorageFile* obj); void storage_file_init_set(StorageFile* obj, const StorageFile* src); void storage_file_set(StorageFile* obj, const StorageFile* src); void storage_file_clear(StorageFile* obj); +const char* storage_file_get_path(File* file, StorageData* storage); void storage_data_init(StorageData* storage); StorageStatus storage_data_status(StorageData* storage); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 0bb3133358..971f1df171 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -4,6 +4,7 @@ #include #include "sd_notify.h" #include +#include typedef FIL SDFile; typedef DIR SDDir; @@ -741,6 +742,20 @@ FS_Error storage_process_virtual_format(StorageData* storage) { SDError error = f_mkfs(sd_data->path, FM_ANY, 0, work, _MAX_SS); free(work); if(error != FR_OK) return FSE_INTERNAL; + + if(storage_process_virtual_mount(storage) == FSE_OK) { + // Image file path + const char* img_path = storage_file_get_path(mnt_image, mnt_image_storage); + // Image file name + FuriString* img_name = furi_string_alloc(); + path_extract_filename_no_ext(img_path, img_name); + // Label with drive id prefix + char* label = storage_ext_drive_path(storage, furi_string_get_cstr(img_name)); + furi_string_free(img_name); + f_setlabel(label); + free(label); + storage_process_virtual_unmount(storage); + } return FSE_OK; #endif } diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 8605020233..be94a64d2a 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -3,7 +3,13 @@ let notify = require("notification"); let flipper = require("flipper"); let dialog = require("dialog"); -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); +badusb.setup({ + vid: 0xAAAA, + pid: 0xBBBB, + mfr_name: "Flipper", + prod_name: "Zero", + layout_path: "/ext/badusb/assets/layouts/en-US.kl" +}); dialog.message("BadUSB demo", "Press OK to start"); if (badusb.isConnected()) { diff --git a/applications/system/js_app/examples/apps/Scripts/storage.js b/applications/system/js_app/examples/apps/Scripts/storage.js index c19f0f003b..fd2ba4a4fb 100644 --- a/applications/system/js_app/examples/apps/Scripts/storage.js +++ b/applications/system/js_app/examples/apps/Scripts/storage.js @@ -1,19 +1,36 @@ let storage = require("storage"); let path = "/ext/storage.test"; +function arraybuf_to_string(arraybuf) { + let string = ""; + let data_view = Uint8Array(arraybuf); + for (let i = 0; i < data_view.length; i++) { + string += chr(data_view[i]); + } + return string; +} + print("File exists:", storage.exists(path)); print("Writing..."); +// write(path, data, offset) +// If offset is specified, the file is not cleared, content is kept and data is written at specified offset +// Takes both strings and array buffers storage.write(path, "Hello "); print("File exists:", storage.exists(path)); // Append will create the file even if it doesnt exist! +// Takes both strings and array buffers storage.append(path, "World!"); print("Reading..."); +// read(path, size, offset) +// If no size specified, total filesize is used +// If offset is specified, size is capped at (filesize - offset) let data = storage.read(path); -print(data); +// read returns an array buffer, to allow proper usage of raw binary data +print(arraybuf_to_string(data)); print("Removing...") storage.remove(path); @@ -21,6 +38,9 @@ storage.remove(path); print("Done") // There's also: +// storage.copy(old_path, new_path); +// storage.move(old_path, new_path); +// storage.mkdir(path); // storage.virtualInit(path); // storage.virtualMount(); // storage.virtualQuit(); \ No newline at end of file diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index a380141e84..d2e30b3342 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -2,8 +2,11 @@ #include "../js_modules.h" #include +#define ASCII_TO_KEY(layout, x) (((uint8_t)x < 128) ? (layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + typedef struct { FuriHalUsbHidConfig* hid_cfg; + uint16_t layout[128]; FuriHalUsbInterface* usb_if_prev; uint8_t key_hold_cnt; } JsBadusbInst; @@ -85,10 +88,15 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { } if(badusb->hid_cfg) { free(badusb->hid_cfg); + badusb->hid_cfg = NULL; } } -static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { +static bool setup_parse_params( + JsBadusbInst* badusb, + struct mjs* mjs, + mjs_val_t arg, + FuriHalUsbHidConfig* hid_cfg) { if(!mjs_is_object(arg)) { return false; } @@ -96,6 +104,7 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t layout_obj = mjs_get(mjs, arg, "layout_path", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { hid_cfg->vid = mjs_get_int32(mjs, vid_obj); @@ -122,6 +131,25 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product)); } + if(mjs_is_string(layout_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len); + if((str_len == 0) || (str_temp == NULL)) { + return false; + } + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bool layout_loaded = storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) && + storage_file_read(file, badusb->layout, sizeof(badusb->layout)) == + sizeof(badusb->layout); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + if(!layout_loaded) { + return false; + } + } else { + memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(badusb->layout))); + } + return true; } @@ -144,7 +172,7 @@ static void js_badusb_setup(struct mjs* mjs) { } else if(num_args == 1) { badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); // Parse argument object - args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg); + args_correct = setup_parse_params(badusb, mjs, mjs_arg(mjs, 0), badusb->hid_cfg); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -191,9 +219,9 @@ static void js_badusb_is_connected(struct mjs* mjs) { mjs_return(mjs, mjs_mk_boolean(mjs, is_connected)); } -uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { +uint16_t get_keycode_by_name(JsBadusbInst* badusb, const char* key_name, size_t name_len) { if(name_len == 1) { // Single char - return (HID_ASCII_TO_KEY(key_name[0])); + return (ASCII_TO_KEY(badusb->layout, key_name[0])); } for(size_t i = 0; i < COUNT_OF(key_codes); i++) { @@ -210,7 +238,7 @@ uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { return HID_KEYBOARD_NONE; } -static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { +static bool parse_keycode(JsBadusbInst* badusb, struct mjs* mjs, size_t nargs, uint16_t* keycode) { uint16_t key_tmp = 0; for(size_t i = 0; i < nargs; i++) { mjs_val_t arg = mjs_arg(mjs, i); @@ -221,7 +249,7 @@ static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { // String error return false; } - uint16_t str_key = get_keycode_by_name(key_name, name_len); + uint16_t str_key = get_keycode_by_name(badusb, key_name, name_len); if(str_key == HID_KEYBOARD_NONE) { // Unknown key code return false; @@ -259,7 +287,7 @@ static void js_badusb_press(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -285,7 +313,7 @@ static void js_badusb_hold(struct mjs* mjs) { uint16_t keycode = HID_KEYBOARD_NONE; size_t num_args = mjs_nargs(mjs); if(num_args > 0) { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -324,7 +352,7 @@ static void js_badusb_release(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); return; } else { - args_correct = parse_keycode(mjs, num_args, &keycode); + args_correct = parse_keycode(badusb, mjs, num_args, &keycode); } if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); @@ -347,14 +375,14 @@ static void ducky_numlock_on() { } // Simulate pressing a character using ALT+Numpad ASCII code -static void ducky_altchar(const char* ascii_code) { +static void ducky_altchar(JsBadusbInst* badusb, const char* ascii_code) { // Hold the ALT key furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); // Press the corresponding numpad key for each digit of the ASCII code for(size_t i = 0; ascii_code[i] != '\0'; i++) { char digitChar[5] = {'N', 'U', 'M', ascii_code[i], '\0'}; // Construct the numpad key name - uint16_t numpad_keycode = get_keycode_by_name(digitChar, strlen(digitChar)); + uint16_t numpad_keycode = get_keycode_by_name(badusb, digitChar, strlen(digitChar)); if(numpad_keycode == HID_KEYBOARD_NONE) { continue; // Skip if keycode not found } @@ -420,9 +448,9 @@ static void badusb_print(struct mjs* mjs, bool ln, bool alt) { // Convert character to ascii numeric value char ascii_str[4]; snprintf(ascii_str, sizeof(ascii_str), "%u", (uint8_t)text_str[i]); - ducky_altchar(ascii_str); + ducky_altchar(badusb, ascii_str); } else { - uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + uint16_t keycode = ASCII_TO_KEY(badusb->layout, text_str[i]); furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); } diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 05f8ffd40a..67c17c486f 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -32,8 +32,8 @@ static bool check_arg_count(struct mjs* mjs, size_t count) { return true; } -static bool get_path_arg(struct mjs* mjs, const char** path) { - mjs_val_t path_obj = mjs_arg(mjs, 0); +static bool get_path_arg(struct mjs* mjs, const char** path, size_t index) { + mjs_val_t path_obj = mjs_arg(mjs, index); if(!mjs_is_string(path_obj)) { ret_bad_args(mjs, "Path must be a string"); return false; @@ -49,10 +49,9 @@ static bool get_path_arg(struct mjs* mjs, const char** path) { static void js_storage_read(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; File* file = storage_file_alloc(storage->api); do { @@ -62,15 +61,26 @@ static void js_storage_read(struct mjs* mjs) { } uint64_t size = storage_file_size(file); - if(size > 128 * 1024) { - ret_int_err(mjs, "File too large"); + mjs_val_t size_arg = mjs_arg(mjs, 1); + if(mjs_is_number(size_arg)) { + size = mjs_get_int32(mjs, size_arg); + } + + mjs_val_t seek_arg = mjs_arg(mjs, 2); + if(mjs_is_number(seek_arg)) { + storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); + size = MIN(size, storage_file_size(file) - storage_file_tell(file)); + } + + if(size > memmgr_heap_get_max_free_block()) { + ret_int_err(mjs, "Read size too large"); break; } uint8_t* data = malloc(size); size_t read = storage_file_read(file, data, size); if(read == size) { - mjs_return(mjs, mjs_mk_string(mjs, (const char*)data, size, true)); + mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)data, size)); } else { ret_int_err(mjs, "File read failed"); } @@ -81,27 +91,41 @@ static void js_storage_read(struct mjs* mjs) { static void js_storage_write(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; - mjs_val_t data_obj = mjs_arg(mjs, 1); - if(!mjs_is_string(data_obj)) { - ret_bad_args(mjs, "Data must be a string"); + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { + ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); return; } + if(mjs_is_data_view(data_arg)) { + data_arg = mjs_dataview_get_buf(mjs, data_arg); + } size_t data_len = 0; - const char* data = mjs_get_string(mjs, &data_obj, &data_len); - if((data_len == 0) || (data == NULL)) { - ret_bad_args(mjs, "Bad data argument"); - return; + const char* data = NULL; + if(mjs_is_string(data_arg)) { + data = mjs_get_string(mjs, &data_arg, &data_len); + } else if(mjs_is_typed_array(data_arg)) { + data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); } + mjs_val_t seek_arg = mjs_arg(mjs, 2); + File* file = storage_file_alloc(storage->api); - if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + if(!storage_file_open( + file, + path, + FSAM_WRITE, + mjs_is_number(seek_arg) ? FSOM_OPEN_ALWAYS : FSOM_CREATE_ALWAYS)) { ret_int_err(mjs, storage_file_get_error_desc(file)); + } else { + if(mjs_is_number(seek_arg)) { + storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); + } + size_t write = storage_file_write(file, data, data_len); mjs_return(mjs, mjs_mk_boolean(mjs, write == data_len)); } @@ -113,18 +137,22 @@ static void js_storage_append(struct mjs* mjs) { if(!check_arg_count(mjs, 2)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; - mjs_val_t data_obj = mjs_arg(mjs, 1); - if(!mjs_is_string(data_obj)) { - ret_bad_args(mjs, "Data must be a string"); + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { + ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); return; } + if(mjs_is_data_view(data_arg)) { + data_arg = mjs_dataview_get_buf(mjs, data_arg); + } size_t data_len = 0; - const char* data = mjs_get_string(mjs, &data_obj, &data_len); - if((data_len == 0) || (data == NULL)) { - ret_bad_args(mjs, "Bad data argument"); - return; + const char* data = NULL; + if(mjs_is_string(data_arg)) { + data = mjs_get_string(mjs, &data_arg, &data_len); + } else if(mjs_is_typed_array(data_arg)) { + data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); } File* file = storage_file_alloc(storage->api); @@ -142,7 +170,7 @@ static void js_storage_exists(struct mjs* mjs) { if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage->api, path))); } @@ -152,17 +180,63 @@ static void js_storage_remove(struct mjs* mjs) { if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage->api, path))); } +static void js_storage_copy(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + const char* old_path; + if(!get_path_arg(mjs, &old_path, 0)) return; + + const char* new_path; + if(!get_path_arg(mjs, &new_path, 1)) return; + + FS_Error error = storage_common_copy(storage->api, old_path, new_path); + if(error == FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + } else { + ret_int_err(mjs, storage_error_get_desc(error)); + } +} + +static void js_storage_move(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + const char* old_path; + if(!get_path_arg(mjs, &old_path, 0)) return; + + const char* new_path; + if(!get_path_arg(mjs, &new_path, 1)) return; + + FS_Error error = storage_common_rename(storage->api, old_path, new_path); + if(error == FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + } else { + ret_int_err(mjs, storage_error_get_desc(error)); + } +} + +static void js_storage_mkdir(struct mjs* mjs) { + JsStorageInst* storage = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + const char* path; + if(!get_path_arg(mjs, &path, 0)) return; + + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage->api, path))); +} + static void js_storage_virtual_init(struct mjs* mjs) { JsStorageInst* storage = get_this_ctx(mjs); if(!check_arg_count(mjs, 1)) return; const char* path; - if(!get_path_arg(mjs, &path)) return; + if(!get_path_arg(mjs, &path, 0)) return; if(storage->virtual) { ret_int_err(mjs, "Virtual already setup"); @@ -202,11 +276,6 @@ static void js_storage_virtual_mount(struct mjs* mjs) { return; } - if(storage->virtual) { - storage_file_free(storage->virtual); - storage->virtual = NULL; - } - mjs_return(mjs, MJS_UNDEFINED); } @@ -219,6 +288,11 @@ static void js_storage_virtual_quit(struct mjs* mjs) { return; } + if(storage->virtual) { + storage_file_free(storage->virtual); + storage->virtual = NULL; + } + mjs_return(mjs, MJS_UNDEFINED); } @@ -231,6 +305,9 @@ static void* js_storage_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, storage_obj, "append", ~0, MJS_MK_FN(js_storage_append)); mjs_set(mjs, storage_obj, "exists", ~0, MJS_MK_FN(js_storage_exists)); mjs_set(mjs, storage_obj, "remove", ~0, MJS_MK_FN(js_storage_remove)); + mjs_set(mjs, storage_obj, "copy", ~0, MJS_MK_FN(js_storage_copy)); + mjs_set(mjs, storage_obj, "move", ~0, MJS_MK_FN(js_storage_move)); + mjs_set(mjs, storage_obj, "mkdir", ~0, MJS_MK_FN(js_storage_mkdir)); mjs_set(mjs, storage_obj, "virtualInit", ~0, MJS_MK_FN(js_storage_virtual_init)); mjs_set(mjs, storage_obj, "virtualMount", ~0, MJS_MK_FN(js_storage_virtual_mount)); mjs_set(mjs, storage_obj, "virtualQuit", ~0, MJS_MK_FN(js_storage_virtual_quit)); diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_0.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_0.png new file mode 100644 index 0000000000..753497fae0 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_1.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_1.png new file mode 100644 index 0000000000..aedd7e85ab Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_10.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_10.png new file mode 100644 index 0000000000..d573c781ba Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_11.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_11.png new file mode 100644 index 0000000000..86c1fb73b5 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_12.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_12.png new file mode 100644 index 0000000000..061f9a26f4 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_13.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_13.png new file mode 100644 index 0000000000..bcb0bc77a9 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_14.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_14.png new file mode 100644 index 0000000000..9f9a69250b Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_15.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_15.png new file mode 100644 index 0000000000..351f2c9b82 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_16.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_16.png new file mode 100644 index 0000000000..e77cb28ecf Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_17.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_17.png new file mode 100644 index 0000000000..8c1e85a023 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_18.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_18.png new file mode 100644 index 0000000000..cbaadf12b7 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_19.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_19.png new file mode 100644 index 0000000000..0e926df253 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_2.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_2.png new file mode 100644 index 0000000000..67b294b316 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_20.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_20.png new file mode 100644 index 0000000000..7a80cf5657 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_21.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_21.png new file mode 100644 index 0000000000..69ad187bb8 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_22.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_22.png new file mode 100644 index 0000000000..c83f6d7412 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_23.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_23.png new file mode 100644 index 0000000000..f3a007fb3b Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_24.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_24.png new file mode 100644 index 0000000000..dd8421f869 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_25.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_25.png new file mode 100644 index 0000000000..266d3cd7db Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_26.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_26.png new file mode 100644 index 0000000000..66e3078982 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_27.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_27.png new file mode 100644 index 0000000000..ab033810a9 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_28.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_28.png new file mode 100644 index 0000000000..897dcb051e Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_29.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_29.png new file mode 100644 index 0000000000..0aa3c865ac Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_3.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_3.png new file mode 100644 index 0000000000..7d3365ee4d Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_30.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_30.png new file mode 100644 index 0000000000..1142b205b5 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_31.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_31.png new file mode 100644 index 0000000000..f1c13be32f Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_32.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_32.png new file mode 100644 index 0000000000..67ab4b5898 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_33.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_33.png new file mode 100644 index 0000000000..80f761c2bb Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_34.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_34.png new file mode 100644 index 0000000000..56f509c5b4 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_35.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_35.png new file mode 100644 index 0000000000..50cc954114 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_36.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_36.png new file mode 100644 index 0000000000..e6b6569239 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_37.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_37.png new file mode 100644 index 0000000000..dfde848c2a Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_38.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_38.png new file mode 100644 index 0000000000..873f7650d1 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_39.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_39.png new file mode 100644 index 0000000000..9c6f7110ce Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_39.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_4.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_4.png new file mode 100644 index 0000000000..b5dd2c1b95 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_40.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_40.png new file mode 100644 index 0000000000..c6603542e1 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_40.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_41.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_41.png new file mode 100644 index 0000000000..b12c4068df Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_41.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_42.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_42.png new file mode 100644 index 0000000000..3e3415af66 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_42.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_43.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_43.png new file mode 100644 index 0000000000..d918354a43 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_43.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_44.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_44.png new file mode 100644 index 0000000000..4b3e29fe10 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_44.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_45.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_45.png new file mode 100644 index 0000000000..78d6ab85e8 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_45.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_5.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_5.png new file mode 100644 index 0000000000..544848d81e Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_6.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_6.png new file mode 100644 index 0000000000..9648bad939 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_7.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_7.png new file mode 100644 index 0000000000..d6078bdf1b Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_8.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_8.png new file mode 100644 index 0000000000..0e018b6df2 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/frame_9.png b/assets/dolphin/external/L1_3d_printing_128x64/frame_9.png new file mode 100644 index 0000000000..8fb2567b09 Binary files /dev/null and b/assets/dolphin/external/L1_3d_printing_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_3d_printing_128x64/meta.txt b/assets/dolphin/external/L1_3d_printing_128x64/meta.txt new file mode 100644 index 0000000000..2fa7c72d27 --- /dev/null +++ b/assets/dolphin/external/L1_3d_printing_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 46 +Active frames: 0 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 +Active cycles: 0 +Frame rate: 2 +Duration: 360 +Active cooldown: 0 + +Bubble slots: 0 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_0.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_0.png new file mode 100644 index 0000000000..db2fc0b46c Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_1.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_1.png new file mode 100644 index 0000000000..7b0bfff936 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_2.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_2.png new file mode 100644 index 0000000000..8a33d5543a Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_3.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_3.png new file mode 100644 index 0000000000..6b81624293 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_4.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_4.png new file mode 100644 index 0000000000..15678ed075 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_5.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_5.png new file mode 100644 index 0000000000..3b95fb5a46 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_6.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_6.png new file mode 100644 index 0000000000..020921c3a5 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/frame_7.png b/assets/dolphin/external/L1_Wardriving_128x64/frame_7.png new file mode 100644 index 0000000000..6431f94353 Binary files /dev/null and b/assets/dolphin/external/L1_Wardriving_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Wardriving_128x64/meta.txt b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt new file mode 100644 index 0000000000..5a492afc63 --- /dev/null +++ b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 0 +Frames order: 0 1 2 3 4 5 6 7 7 +Active cycles: 0 +Frame rate: 2 +Duration: 360 +Active cooldown: 0 + +Bubble slots: 1 + +Slot: 0 +X: 55 +Y: 14 +Text: Pwned! +AlignH: Left +AlignV: Center +StartFrame: 7 +EndFrame: 8 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 50689ded19..d5e24b143c 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -189,3 +189,17 @@ Max butthurt: 14 Min level: 16 Max level: 30 Weight: 5 + +Name: L1_3d_printing_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 6 +Max level: 30 +Weight: 6 + +Name: L1_Wardriving_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 11 +Max level: 30 +Weight: 6 diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png index 9a48d15f7a..c804c6a152 100644 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png and b/assets/dolphin/internal/L1_NoSd_128x49/frame_0.png differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_1.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_1.png deleted file mode 100644 index b58936d817..0000000000 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_1.png and /dev/null differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png deleted file mode 100644 index 5b474fff26..0000000000 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_2.png and /dev/null differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png deleted file mode 100644 index 952f968fb6..0000000000 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_3.png and /dev/null differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png deleted file mode 100644 index 2bb43b306f..0000000000 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_4.png and /dev/null differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png b/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png deleted file mode 100644 index d7f8c6402a..0000000000 Binary files a/assets/dolphin/internal/L1_NoSd_128x49/frame_5.png and /dev/null differ diff --git a/assets/dolphin/internal/L1_NoSd_128x49/meta.txt b/assets/dolphin/internal/L1_NoSd_128x49/meta.txt index 08b1f9d9d0..e9514d5617 100644 --- a/assets/dolphin/internal/L1_NoSd_128x49/meta.txt +++ b/assets/dolphin/internal/L1_NoSd_128x49/meta.txt @@ -3,21 +3,12 @@ Version: 1 Width: 128 Height: 49 -Passive frames: 10 +Passive frames: 1 Active frames: 0 -Frames order: 0 1 0 1 0 2 3 4 3 5 +Frames order: 0 Active cycles: 0 -Frame rate: 2 +Frame rate: 1 Duration: 3600 Active cooldown: 0 -Bubble slots: 1 - -Slot: 0 -X: 40 -Y: 18 -Text: Need an\nSD card -AlignH: Right -AlignV: Bottom -StartFrame: 0 -EndFrame: 9 +Bubble slots: 0 diff --git a/assets/icons/Dolphin/DolphinWait_59x54.png b/assets/icons/Dolphin/DolphinWait_59x54.png new file mode 100644 index 0000000000..bdf8171b80 Binary files /dev/null and b/assets/icons/Dolphin/DolphinWait_59x54.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png b/assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png new file mode 100644 index 0000000000..81a0e29ba5 Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinDone_80x58.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png b/assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png new file mode 100644 index 0000000000..ccaf12307a Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinMafia_119x62.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png b/assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png new file mode 100644 index 0000000000..b700ba94da Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinReadingSuccess_59x63.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png b/assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png new file mode 100644 index 0000000000..4b3aedaa99 Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinSaved_92x58.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png b/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png new file mode 100644 index 0000000000..90b5befec1 Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinSuccess_91x55.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png b/assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png new file mode 100644 index 0000000000..c4ca5a174e Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/DolphinWait_61x59.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png b/assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png new file mode 100644 index 0000000000..5595b606c7 Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/WarningDolphinFlip_45x42.png differ diff --git a/assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png b/assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png new file mode 100644 index 0000000000..b254b6bca2 Binary files /dev/null and b/assets/packs/Momentum/Icons/Dolphin/WarningDolphin_45x42.png differ diff --git a/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png b/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png index c8d8ff8ef4..853e95202a 100644 Binary files a/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png and b/assets/packs/Momentum/Icons/NFC/NFC_dolphin_emulation_51x64.png differ diff --git a/assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png b/assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png new file mode 100644 index 0000000000..dacc6aae6c Binary files /dev/null and b/assets/packs/Momentum/Icons/RFID/RFIDDolphinReceive_97x61.png differ diff --git a/assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png b/assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png new file mode 100644 index 0000000000..fd2ead2e3d Binary files /dev/null and b/assets/packs/Momentum/Icons/RFID/RFIDDolphinSend_97x61.png differ diff --git a/assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png b/assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png new file mode 100644 index 0000000000..0ae1403da7 Binary files /dev/null and b/assets/packs/Momentum/Icons/iButton/iButtonDolphinVerySuccess_92x55.png differ diff --git a/documentation/JavaScript.md b/documentation/JavaScript.md index f6165e6975..3b88e90530 100644 --- a/documentation/JavaScript.md +++ b/documentation/JavaScript.md @@ -2,6 +2,7 @@ # JavaScript scripting API (WIP) ## Note: This documentation is still work in progress! Todo: +- Migrate to new format from OFW (see /documentation/js/ folder) - Add missing parameters & returns (Dialog and lower) ## Description diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 6fb4d5a717..78055caad0 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -15,6 +15,7 @@ The documentation is divided into several sections, with all of them accessible - @ref dev_tools - Hardware and software tools for all kinds of programming - @ref expansion - Additional modules to expand Flipper's consciousness - @ref misc - Various useful pieces of information +- @ref js - JS-based scripting engine documentation Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox new file mode 100644 index 0000000000..1c81a08350 --- /dev/null +++ b/documentation/doxygen/js.dox @@ -0,0 +1,18 @@ +/** +@page js JavaScript + +This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library + +- [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) +- @subpage js_data_types +- @subpage js_builtin + +JavaScript Modules +JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules: + +- @subpage js_badusb - BadUSB module +- @subpage js_serial - Serial module +- @subpage js_dialog - Dialog module +- @subpage js_notification - Notifications module + +*/ diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md new file mode 100644 index 0000000000..28372e56a4 --- /dev/null +++ b/documentation/js/js_badusb.md @@ -0,0 +1,144 @@ +# js_badusb {#js_badusb} + +# BadUSB module +```js +let badusb = require("badusb"); +``` +# Methods +## setup +Start USB HID with optional parameters. Should be called before all other methods. + +### Parameters +Configuration object (optional): +- vid, pid (number): VID and PID values, both are mandatory +- mfr_name (string): Manufacturer name (32 ASCII characters max), optional +- prod_name (string): Product name (32 ASCII characters max), optional + +### Examples: +```js +// Start USB HID with default parameters +badusb.setup(); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +``` + +## isConnected +Returns USB connection state. + +### Example: +```js +if (badusb.isConnected()) { + // Do something +} else { + // Show an error +} +``` + +## press +Press and release a key. + +### Parameters +Key or modifier name, key code. + +See a list of key names below. + +### Examples: +```js +badusb.press("a"); // Press "a" key +badusb.press("A"); // SHIFT + "a" +badusb.press("CTRL", "a"); // CTRL + "a" +badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo +badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) +badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) +``` + +## hold +Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously. + +### Parameters +Same as `press` + +### Examples: +```js +badusb.hold("a"); // Press and hold "a" key +badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo +``` + +## release +Release a previously hold key. + +### Parameters +Same as `press` + +Release all keys if called without parameters + +### Examples: +```js +badusb.release(); // Release all keys +badusb.release("a"); // Release "a" key +``` + +## print +Print a string. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.print("Hello, world!"); // print "Hello, world!" +badusb.print("Hello, world!", 100); // Add 100ms delay between key presses +``` + +## println +Same as `print` but ended with "ENTER" press. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` + +# Key names list + +## Modifier keys + +| Name | +| ------------- | +| CTRL | +| SHIFT | +| ALT | +| GUI | + +## Special keys + +| Name | Notes | +| ------------------ | ---------------- | +| DOWN | Down arrow | +| LEFT | Left arrow | +| RIGHT | Right arrow | +| UP | Up arrow | +| ENTER | | +| DELETE | | +| BACKSPACE | | +| END | | +| HOME | | +| ESC | | +| INSERT | | +| PAGEUP | | +| PAGEDOWN | | +| CAPSLOCK | | +| NUMLOCK | | +| SCROLLLOCK | | +| PRINTSCREEN | | +| PAUSE | Pause/Break key | +| SPACE | | +| TAB | | +| MENU | Context menu key | +| Fx | F1-F24 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md new file mode 100644 index 0000000000..3d113807b0 --- /dev/null +++ b/documentation/js/js_builtin.md @@ -0,0 +1,56 @@ +# Built-in methods {#js_builtin} + +## require +Load a module plugin. + +### Parameters +- Module name + +### Examples: +```js +let serial = require("serial"); // Load "serial" module +``` + +## delay +### Parameters +- Delay value in ms + +### Examples: +```js +delay(500); // Delay for 500ms +``` +## print +Print a message on a screen console. + +### Parameters +The following argument types are supported: +- String +- Number +- Bool +- undefined + +### Examples: +```js +print("string1", "string2", 123); +``` + +## console.log +## console.warn +## console.error +## console.debug +Same as `print`, but output to serial console only, with corresponding log level. + +## to_string +Convert a number to string. + +### Examples: +```js +to_string(123) +``` +## to_hex_string +Convert a number to string(hex format). + +### Examples: +```js +to_hex_string(0xFF) +``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md new file mode 100644 index 0000000000..de1c896dce --- /dev/null +++ b/documentation/js/js_data_types.md @@ -0,0 +1,13 @@ +# Data types {#js_data_types} + +Here is a list of common data types used by mJS. +- string - sequence of single byte characters, no UTF8 support +- number +- boolean +- foreign - C function or data pointer +- undefined +- null +- object - a data structure with named fields +- array - special type of object, all items have indexes and equal types +- ArrayBuffer - raw data buffer +- DataView - provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md new file mode 100644 index 0000000000..5804b075eb --- /dev/null +++ b/documentation/js/js_dialog.md @@ -0,0 +1,49 @@ +# js_dialog {#js_dialog} + +# Dialog module +```js +let dialog = require("dialog"); +``` +# Methods + +## message +Show a simple message dialog with header, text and "OK" button. + +### Parameters +- Dialog header text +- Dialog text + +### Retuns +true if central button was pressed, false if the dialog was closed by back key press + +### Examples: +```js +dialog.message("Dialog demo", "Press OK to start"); +``` + +## custom +More complex dialog with configurable buttons + +### Parameters +Configuration object with the following fileds: +- header: Dialog header text +- text: Dialog text +- button_left: (optional) left button name +- button_right: (optional) right button name +- button_center: (optional) central button name + +### Retuns +Name of pressed button or empty string if the dialog was closed by back key press + +### Examples: +```js +let dialog_params = ({ + header: "Dialog header", + text: "Dialog text", + button_left: "Left", + button_right: "Right", + button_center: "OK" +}); + +dialog.custom(dialog_params); +``` diff --git a/documentation/js/js_notification.md b/documentation/js/js_notification.md new file mode 100644 index 0000000000..100da44146 --- /dev/null +++ b/documentation/js/js_notification.md @@ -0,0 +1,36 @@ +# js_notification {#js_notification} + +# Notification module +```js +let notify = require("notification"); +``` +# Methods + +## success +"Success" flipper notification message + +### Examples: +```js +notify.success(); +``` + +## error +"Error" flipper notification message + +### Examples: +```js +notify.error(); +``` + +## blink +Blink notification LED + +### Parameters +- Blink color (blue/red/green/yellow/cyan/magenta) +- Blink type (short/long) + +### Examples: +```js +notify.blink("red", "short"); // Short blink of red LED +notify.blink("green", "short"); // Long blink of green LED +``` \ No newline at end of file diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md new file mode 100644 index 0000000000..cd9993a181 --- /dev/null +++ b/documentation/js/js_serial.md @@ -0,0 +1,107 @@ +# js_serial {#js_serial} + +# Serial module +```js +let serial = require("serial"); +``` +# Methods + +## setup +Configure serial port. Should be called before all other methods. + +### Parameters +- Serial port name (usart, lpuart) +- Baudrate + +### Examples: +```js +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); +``` + +## write +Write data to serial port + +### Parameters +One or more arguments of the following types: +- A string +- Single number, each number is interpreted as a byte +- Array of numbers, each number is interpreted as a byte +- ArrayBuffer or DataView + +### Examples: +```js +serial.write(0x0a); // Write a single byte 0x0A +serial.write("Hello, world!"); // Write a string +serial.write("Hello, world!", [0x0d, 0x0a]); // Write a string followed by two bytes +``` + +## read +Read a fixed number of characters from serial port. + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.read(1); // Read a single byte, without timeout +serial.read(10, 5000); // Read 10 bytes, with 5s timeout +``` + +## readln +Read from serial port untill line break character + +### Parameters +(optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readln(); // Read without timeout +serial.readln(5000); // Read with 5s timeout +``` + +## readBytes +Read from serial port untill line break character + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +ArrayBuffer with received data or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readBytes(4); // Read 4 bytes, without timeout + +// Read one byte from receive buffer with zero timeout, returns UNDEFINED if Rx bufer is empty +serial.readBytes(1, 0); +``` + +## expect +Search for a string pattern in received data stream + +### Parameters +- Single argument or array of the following types: + - A string + - Array of numbers, each number is interpreted as a byte +- (optional) Timeout value in ms + +### Returns +Index of matched pattern in input patterns list, undefined if nothing was found. + +### Examples: +```js +// Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not +serial.expect("# ", 1000); + +// Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one +serial.expect([": not found", "Usage: "]); +``` \ No newline at end of file diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768adc05df..768d448904 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,6 +4,8 @@ #include extern void* pvPortMalloc(size_t xSize); +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); +extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -18,18 +20,7 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - if(size == 0) { - vPortFree(ptr); - return NULL; - } - - void* p = pvPortMalloc(size); - if(ptr != NULL) { - memcpy(p, ptr, size); - vPortFree(ptr); - } - - return p; + return pvPortRealloc(ptr, size); } void* calloc(size_t count, size_t size) { @@ -47,6 +38,10 @@ char* strdup(const char* s) { return y; } +void* aligned_alloc(size_t alignment, size_t size) { + return pvPortAllocAligned(size, alignment); +} + size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -79,33 +74,17 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_alloc_from_pool(size_t size) { +void* memmgr_aux_pool_alloc(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_pool_get_free(void) { +size_t memmgr_aux_pool_get_free(void) { return furi_hal_memory_get_free(); } size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); -} - -void* aligned_malloc(size_t size, size_t alignment) { - void* p1; // original block - void** p2; // aligned block - int offset = alignment - 1 + sizeof(void*); - if((p1 = (void*)malloc(size + offset)) == NULL) { - return NULL; - } - p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); - p2[-1] = p1; - return p2; -} - -void aligned_free(void* p) { - free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index bc0c35faa7..796a1f5378 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,37 +36,22 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * An aligned version of malloc, used when you need to get the aligned space on the heap - * Freeing the received address is performed ONLY through the aligned_free function - * @param size - * @param alignment - * @return void* - */ -void* aligned_malloc(size_t size, size_t alignment); - -/** - * Freed space obtained through the aligned_malloc function - * @param p pointer to result of aligned_malloc - */ -void aligned_free(void* p); - -/** - * @brief Allocate memory from separate memory pool. That memory can't be freed. + * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_alloc_from_pool(size_t size); +void* memmgr_aux_pool_alloc(size_t size); /** - * @brief Get free memory pool size + * @brief Get the auxiliary pool free memory size * * @return size_t */ -size_t memmgr_pool_get_free(void); +size_t memmgr_aux_pool_get_free(void); /** - * @brief Get max free block size from memory pool + * @brief Get max free block size from the auxiliary memory pool * * @return size_t */ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3f62b518c2..3dfc7f5acb 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,124 +1,18 @@ -/* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -/* - * A sample implementation of pvPortMalloc() and vPortFree() that combines - * (coalescences) adjacent memory blocks as they are freed, and in so doing - * limits memory fragmentation. - * - * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. - */ - -#include "memmgr_heap.h" -#include "check.h" -#include -#include -#include -#include -#include - -/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ -#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE - +#include +#include +#include #include #include +#include -#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE - -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT -#endif - -#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) -#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 -#endif - -/* Block sizes must not get too small. */ -#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) - -/* Assumes 8bit bytes! */ -#define heapBITS_PER_BYTE ((size_t)8) - -/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; -uint8_t* ucHeap = (uint8_t*)&__heap_start__; - -/* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ -typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ -} BlockLink_t; - -/*-----------------------------------------------------------*/ - -/* - * Inserts a block of memory that is being freed into the correct position in - * the list of free memory blocks. The block being freed will be merged with - * the block in front it and/or the block behind it if the memory blocks are - * adjacent to each other. - */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); - -/* - * Called automatically to setup the required heap structures the first time - * pvPortMalloc() is called. - */ -static void prvHeapInit(void); - -/*-----------------------------------------------------------*/ - -/* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ -static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & - ~((size_t)portBYTE_ALIGNMENT_MASK); - -/* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; - -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; - -/* Furi heap extension */ -#include -/* Allocation tracking types */ +static tlsf_t tlsf = NULL; +static size_t heap_used = 0; +static size_t heap_max_used = 0; + +// Allocation tracking types DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 DICT_DEF2( //-V1048 @@ -128,17 +22,35 @@ DICT_DEF2( //-V1048 MemmgrHeapAllocDict_t, DICT_OPLIST(MemmgrHeapAllocDict)) -/* Thread allocation tracing storage */ +// Thread allocation tracing storage static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; static volatile uint32_t memmgr_heap_thread_trace_depth = 0; -/* Initialize tracing storage on start */ -void memmgr_heap_init(void) { +static inline void memmgr_lock(void) { + vTaskSuspendAll(); +} + +static inline void memmgr_unlock(void) { + xTaskResumeAll(); +} + +static inline size_t memmgr_get_heap_size(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; +} + +// Initialize tracing storage +static void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } +__attribute__((constructor)) static void memmgr_init(void) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); @@ -148,517 +60,289 @@ void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { MemmgrHeapAllocDict_clear(alloc_dict); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } -size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - vTaskSuspendAll(); - { +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - uint8_t* puc = (uint8_t*)data->key; - puc -= xHeapStructSize; - BlockLink_t* pxLink = (void*)puc; - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && - pxLink->pxNextFreeBlock == NULL) { - leftovers += data->value; - } - } - } + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); } memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); - return leftovers; } -#undef traceMALLOC -static inline void traceMALLOC(void* pointer, size_t size) { +static inline void memmgr_heap_trace_free(void* pointer) { FuriThreadId thread_id = furi_thread_get_current_id(); if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } } -#undef traceFREE -static inline void traceFREE(void* pointer, size_t size) { - UNUSED(size); - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + memmgr_lock(); + { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + block_header_t* block = block_from_ptr((uint8_t*)data->key); + if(!block_is_free(block)) { + leftovers += data->value; + } + } + } } memmgr_heap_thread_trace_depth--; } + memmgr_unlock(); + return leftovers; } -size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); +static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { + UNUSED(ptr); - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; + bool free = !used; + size_t* max_free_block_size = (size_t*)user; + if(free && size > *max_free_block_size) { + *max_free_block_size = size; } - xTaskResumeAll(); - return max_free_size; + return true; } -void memmgr_heap_printf_free_blocks(void) { - BlockLink_t* pxBlock; - //TODO enable when we can do printf with a locked scheduler - //vTaskSuspendAll(); +size_t memmgr_heap_get_max_free_block(void) { + size_t max_free_block_size = 0; - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; - } + memmgr_lock(); - //xTaskResumeAll(); -} + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. + memmgr_unlock(); - return str; + return max_free_block_size; } -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} +typedef struct { + BlockWalker walker; + void* context; +} BlockWalkerWrapper; -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); +static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { + BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; + return wrapper->walker(ptr, size, used, wrapper->context); } -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } +void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { + memmgr_lock(); - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif -/*-----------------------------------------------------------*/ + BlockWalkerWrapper wrapper = {walker, context}; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); -void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; - void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + memmgr_unlock(); +} +void* pvPortMalloc(size_t xSize) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif - - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif - - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + memmgr_lock(); + + // allocate block + void* data = tlsf_malloc(tlsf, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc(0)"); + } else { + furi_crash("out of memory"); } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); } - vTaskSuspendAll(); - { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } - if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { - /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ - pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { - pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; - } + // trace allocation + memmgr_heap_trace_malloc(data, xSize); - /* If the end marker was reached then a block of adequate size - was not found. */ - if(pxBlock != pxEnd) { - /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); - - /* This block is being returned for use so must be taken out - of the list of free blocks. */ - pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; - - /* If the block is larger than required it can be split into - two. */ - if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { - /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ - pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); - configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); - - /* Calculate the sizes of two blocks split from the - single block. */ - pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; - pxBlock->xBlockSize = xWantedSize; - - /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); - } else { - mtCOVERAGE_TEST_MARKER(); - } + memmgr_unlock(); - xFreeBytesRemaining -= pxBlock->xBlockSize; + // clear block content + memset(data, 0, xSize); - if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { - xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; - } else { - mtCOVERAGE_TEST_MARKER(); - } + return data; +} - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; +void vPortFree(void* pv) { + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // ignore NULL pointer + if(pv != NULL) { + memmgr_lock(); - traceMALLOC(pvReturn, xWantedSize); - } - (void)xTaskResumeAll(); + // get block size + size_t block_size = tlsf_block_size(pv); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif + // clear block content + memset(pv, 0, block_size); -#if(configUSE_MALLOC_FAILED_HOOK == 1) - { - if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); - vApplicationMallocFailedHook(); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } -#endif + // update heap usage + heap_used -= block_size; + heap_used -= tlsf_alloc_overhead(); - configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); + // free + tlsf_free(tlsf, pv); - furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); - return pvReturn; -} -/*-----------------------------------------------------------*/ + // trace free + memmgr_heap_trace_free(pv); -void vPortFree(void* pv) { - uint8_t* puc = (uint8_t*)pv; - BlockLink_t* pxLink; + memmgr_unlock(); + } +} +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - if(pv != NULL) { - /* The memory being freed will have an BlockLink_t structure immediately - before it. */ - puc -= xHeapStructSize; - - /* This casting is to keep the compiler from issuing warnings. */ - pxLink = (void*)puc; - - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { - /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); -#endif - - vTaskSuspendAll(); - { - furi_assert((size_t)pv >= SRAM_BASE); - furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); - furi_assert(pxLink->xBlockSize >= xHeapStructSize); - furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); - - /* Add this block to the list of free blocks. */ - xFreeBytesRemaining += pxLink->xBlockSize; - traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); - } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); - } + // alignment must be power of 2 + if((xAlignment & (xAlignment - 1)) != 0) { + furi_crash("invalid alignment"); + } + + memmgr_lock(); + + // allocate block + void* data = tlsf_memalign(tlsf, xAlignment, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc_aligned(0)"); } else { - mtCOVERAGE_TEST_MARKER(); + furi_crash("out of memory"); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } -} -/*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } -size_t xPortGetFreeHeapSize(void) { - return xFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + // trace allocation + memmgr_heap_trace_malloc(data, xSize); -size_t xPortGetMinimumEverFreeHeapSize(void) { - return xMinimumEverFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + memmgr_unlock(); + + // clear block content + memset(data, 0, xSize); -void vPortInitialiseBlocks(void) { - /* This just exists to keep the linker quiet. */ + return data; } -/*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +extern void* pvPortRealloc(void* pv, size_t xSize) { + // realloc(ptr, 0) is equivalent to free(ptr) + if(xSize == 0) { + vPortFree(pv); + return NULL; + } + + // realloc(NULL, size) is equivalent to malloc(size) + if(pv == NULL) { + return pvPortMalloc(xSize); + } - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + /* realloc things */ - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); } - pucAlignedHeap = (uint8_t*)uxAddress; - - /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; - xStart.xBlockSize = (size_t)0; - - /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; - pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; - - /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; - - /* Only one block exists - and it covers the entire usable heap space. */ - xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); -} -/*-----------------------------------------------------------*/ + memmgr_lock(); -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { - BlockLink_t* pxIterator; - uint8_t* puc; + // trace old block as free + size_t old_size = tlsf_block_size(pv); - /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { - /* Nothing to do here, just iterate to the right position. */ - } + // trace free + memmgr_heap_trace_free(pv); - /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ - puc = (uint8_t*)pxIterator; - if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { - pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; - pxBlockToInsert = pxIterator; - } else { - mtCOVERAGE_TEST_MARKER(); + // reallocate block + void* data = tlsf_realloc(tlsf, pv, xSize); + if(data == NULL) { + furi_crash("out of memory"); } - /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ - puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { - /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; - } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; - } - } else { - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; + // update heap usage + heap_used -= old_size; + heap_used += tlsf_block_size(data); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ - if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; - } else { - mtCOVERAGE_TEST_MARKER(); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + memmgr_unlock(); + + // clear remain block content, if the new size is bigger + // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize + if(xSize > old_size) { + memset((uint8_t*)data + old_size, 0, xSize - old_size); } + + return data; } + +size_t xPortGetFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); +} + +size_t xPortGetTotalHeapSize(void) { + return memmgr_get_heap_size(); +} + +size_t xPortGetMinimumEverFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); +} \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 7d889f1520..2f61deb642 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,9 +40,17 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -/** Print the address and size of all free blocks to stdout +typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); + +/** + * @brief Walk through all heap blocks + * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. + * Also, printf and furi_log contains malloc calls, so do not use them. + * + * @param walker + * @param context */ -void memmgr_heap_printf_free_blocks(void); +void memmgr_heap_walk_blocks(BlockWalker walker, void* context); #ifdef __cplusplus } diff --git a/furi/core/thread.c b/furi/core/thread.c index ecb21636c7..c3c44ae6db 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -244,8 +244,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_alloc_from_pool(sizeof(StackType_t) * stack), - memmgr_alloc_from_pool(sizeof(StaticTask_t))); + memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), + memmgr_aux_pool_alloc(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/furi/flipper.c b/furi/flipper.c index da4ebd2599..fb35c5f070 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -169,12 +169,17 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; + void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *tcb_ptr = &idle_task_tcb; + *stack_ptr = idle_task_stack; *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -182,7 +187,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); + *tcb_ptr = &timer_task_tcb; + *stack_ptr = timer_task_stack; *stack_size = configTIMER_TASK_STACK_DEPTH; } diff --git a/lib/SConscript b/lib/SConscript index 84247d53c6..61221d87a2 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -14,6 +14,7 @@ env.Append( libs = env.BuildModules( [ + "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index 94bf896328..4e4f9e9912 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -380,8 +380,8 @@ static GapConfig template_config = { .pairing_method = GapPairingPinCodeVerifyYesNo, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 6543168662..e117e546a2 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -461,7 +461,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); + section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { @@ -713,7 +713,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - aligned_free(s->fast_rel->data); + free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -780,10 +780,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - aligned_free(itref->value.data); + free(itref->value.data); } if(itref->value.fast_rel) { - aligned_free(itref->value.fast_rel->data); + free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/lib/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c index ce4392475b..84fcccd71c 100644 --- a/lib/ibutton/ibutton_protocols.c +++ b/lib/ibutton/ibutton_protocols.c @@ -287,6 +287,17 @@ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const return success; } +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_uid(GROUP_DATA, data, PROTOCOL_ID, result); +} + void ibutton_protocols_render_data( iButtonProtocols* protocols, const iButtonKey* key, diff --git a/lib/ibutton/ibutton_protocols.h b/lib/ibutton/ibutton_protocols.h index ec4a9fc723..dd2afbd6ee 100644 --- a/lib/ibutton/ibutton_protocols.h +++ b/lib/ibutton/ibutton_protocols.h @@ -133,6 +133,17 @@ bool ibutton_protocols_save( */ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name); +/** + * Format a string containing defice UID + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + /** * Format a string containing device full data * @param [in] protocols pointer to an iButtonProtocols object diff --git a/lib/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c index 6e99a3be23..7ce12f7a06 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.c +++ b/lib/ibutton/protocols/dallas/dallas_common.c @@ -208,15 +208,26 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { return crc_calculated == crc_received; } +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data) { + furi_string_cat_printf(result, "ID: "); + for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { + furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); + } +} + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, const uint8_t* mem_data, size_t mem_size, const char* mem_name) { + UNUSED(mem_data); + + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", rom_data->bytes[0]); const char* size_prefix = ""; size_t mem_size_bits = mem_size * BITS_IN_BYTE; @@ -229,28 +240,23 @@ void dallas_common_render_brief_data( mem_size_bits /= BITS_IN_KBIT; } - furi_string_cat_printf( - result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); - - for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { - furi_string_cat_printf(result, "%02X ", mem_data[i]); - } - - furi_string_cat_printf(result, "[ . . . ]"); - - for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) { - furi_string_cat_printf(result, " %02X", mem_data[i]); - } + furi_string_cat_printf(result, "%s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); } void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) { - furi_string_set(result, "CRC Error\n"); + furi_string_set(result, "\e#CRC Error\e#\n"); const size_t data_size = sizeof(DallasCommonRomData); for(size_t i = 0; i < data_size; ++i) { - furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]); + furi_string_cat_printf( + result, (i < data_size - 1) ? "%02X " : "\e!%02X\e!", rom_data->bytes[i]); } + + furi_string_cat_printf( + result, + "\nExpected CRC: \e!%02X\e!", + maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT)); } void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) { diff --git a/lib/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h index 6f5ff7cc01..90fec3e28a 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.h +++ b/lib/ibutton/protocols/dallas/dallas_common.h @@ -96,6 +96,8 @@ bool dallas_common_load_rom_data( /* Miscellaneous */ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data); + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, diff --git a/lib/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h index 55e1099360..05620329f8 100644 --- a/lib/ibutton/protocols/dallas/protocol_dallas_base.h +++ b/lib/ibutton/protocols/dallas/protocol_dallas_base.h @@ -30,6 +30,7 @@ typedef struct { iButtonProtocolDallasEmulateFunc emulate; iButtonProtocolDallasSaveFunc save; iButtonProtocolDallasLoadFunc load; + iButtonProtocolDallasRenderDataFunc render_uid; iButtonProtocolDallasRenderDataFunc render_data; iButtonProtocolDallasRenderDataFunc render_brief_data; iButtonProtocolDallasRenderDataFunc render_error; diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index b65e645846..d60803fc6d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -35,6 +35,7 @@ static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1971_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*); @@ -58,6 +59,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, .load = dallas_ds1971_load, + .render_uid = dallas_ds1971_render_uid, .render_data = dallas_ds1971_render_data, .render_brief_data = dallas_ds1971_render_brief_data, .render_error = dallas_ds1971_render_error, @@ -209,14 +211,26 @@ bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1971ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1971_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 86d39f1bd8..67e7545f45 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -27,6 +27,7 @@ static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*); static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1990_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*); static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*); @@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1990 = { .emulate = dallas_ds1990_emulate, .save = dallas_ds1990_save, .load = dallas_ds1990_load, + .render_uid = dallas_ds1990_render_uid, .render_data = NULL, /* No data to render */ .render_brief_data = dallas_ds1990_render_brief_data, .render_error = dallas_ds1990_render_error, @@ -117,12 +119,20 @@ bool dallas_ds1990_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void dallas_ds1990_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1990ProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", data->rom_data.bytes[0]); } void dallas_ds1990_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 7440882ea0..5ddd8ef2ce 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -36,6 +36,7 @@ static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1992_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*); @@ -57,6 +58,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1992 = { .emulate = dallas_ds1992_emulate, .save = dallas_ds1992_save, .load = dallas_ds1992_load, + .render_uid = dallas_ds1992_render_uid, .render_data = dallas_ds1992_render_data, .render_brief_data = dallas_ds1992_render_brief_data, .render_error = dallas_ds1992_render_error, @@ -182,14 +184,26 @@ bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1992_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1992_SRAM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 5970a67bbf..6af61f3552 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -33,6 +33,7 @@ static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1996_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*); @@ -53,6 +54,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { .emulate = dallas_ds1996_emulate, .save = dallas_ds1996_save, .load = dallas_ds1996_load, + .render_uid = dallas_ds1996_render_uid, .render_data = dallas_ds1996_render_data, .render_brief_data = dallas_ds1996_render_brief_data, .render_error = dallas_ds1996_render_error, @@ -207,15 +209,27 @@ bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, DS1996_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1996_SRAM_DATA_SIZE); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); + furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); + + furi_string_free(data_string); } void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index 6c698bb892..101db1dbe2 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -8,7 +8,7 @@ #include "../blanks/tm2004.h" #define DALLAS_GENERIC_FAMILY_CODE 0x00U -#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric" +#define DALLAS_GENERIC_FAMILY_NAME "(non-specific)" typedef struct { OneWireSlave* bus; @@ -24,6 +24,7 @@ static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*); static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*); static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*); +static void ds_generic_render_uid(FuriString*, const iButtonProtocolData*); static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*); static void ds_generic_render_error(FuriString*, const iButtonProtocolData*); static bool ds_generic_is_data_valid(const iButtonProtocolData*); @@ -44,6 +45,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds_generic = { .save = ds_generic_save, .load = ds_generic_load, .render_data = NULL, /* No data to render */ + .render_uid = ds_generic_render_uid, .render_brief_data = ds_generic_render_brief_data, .render_error = ds_generic_render_error, .is_valid = ds_generic_is_data_valid, @@ -111,9 +113,15 @@ bool ds_generic_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void ds_generic_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DallasGenericProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DallasGenericProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c index 39cabbeb45..7dad756690 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas.c @@ -51,6 +51,12 @@ static bool ibutton_protocol_group_dallas_get_id_by_name( return true; } + // Handle files that refer to Dallas "Raw Data" as DSGeneric + if(strcmp(name, "DSGeneric") == 0) { + *id = iButtonProtocolDSGeneric; + return true; + } + for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) { if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) { *id = i; @@ -212,6 +218,18 @@ static bool ibutton_protocol_group_dallas_load( return ibutton_protocols_dallas[id]->load(ff, version, data); } +static void ibutton_protocol_group_dallas_render_uid( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->render_uid); + protocol->render_uid(result, data); +} + static void ibutton_protocol_group_dallas_render_data( iButtonProtocolGroupDallas* group, const iButtonProtocolData* data, @@ -298,6 +316,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_dallas = { .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save, .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load, + .render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_uid, .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data, .render_brief_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data, diff --git a/lib/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c index 0cdb8766b0..d38865bae2 100644 --- a/lib/ibutton/protocols/misc/protocol_cyfral.c +++ b/lib/ibutton/protocols/misc/protocol_cyfral.c @@ -325,7 +325,15 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } +static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) { + furi_string_cat_printf(result, "ID: "); + for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { + furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); + } +} + static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) { + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); } @@ -348,5 +356,6 @@ const ProtocolBase ibutton_protocol_misc_cyfral = { .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_cyfral_render_uid, .render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data, }; diff --git a/lib/ibutton/protocols/misc/protocol_metakom.c b/lib/ibutton/protocols/misc/protocol_metakom.c index a2bd2cf7ca..6d5e0339d6 100644 --- a/lib/ibutton/protocols/misc/protocol_metakom.c +++ b/lib/ibutton/protocols/misc/protocol_metakom.c @@ -301,12 +301,17 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { return result; } -static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { +static void protocol_metakom_render_uid(ProtocolMetakom* proto, FuriString* result) { + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); } } +static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { + protocol_metakom_render_uid(proto, result); +} + const ProtocolBase ibutton_protocol_misc_metakom = { .name = "Metakom", .manufacturer = "Metakom", @@ -324,5 +329,6 @@ const ProtocolBase ibutton_protocol_misc_metakom = { .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_metakom_render_uid, .render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data, }; diff --git a/lib/ibutton/protocols/protocol_group_base.h b/lib/ibutton/protocols/protocol_group_base.h index c8fec70fe7..ef57fe0bc0 100644 --- a/lib/ibutton/protocols/protocol_group_base.h +++ b/lib/ibutton/protocols/protocol_group_base.h @@ -93,6 +93,7 @@ typedef struct { iButtonProtocolGroupSaveFunc save; iButtonProtocolGroupLoadFunc load; + iButtonProtocolGroupRenderFunc render_uid; iButtonProtocolGroupRenderFunc render_data; iButtonProtocolGroupRenderFunc render_brief_data; iButtonProtocolGroupRenderFunc render_error; diff --git a/lib/momentum/momentum.h b/lib/momentum/momentum.h index b5eeec02f2..ea2c86cc46 100644 --- a/lib/momentum/momentum.h +++ b/lib/momentum/momentum.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,7 @@ typedef struct { VgmColorMode vgm_color_mode; Rgb565Color vgm_color_fg; Rgb565Color vgm_color_bg; + FuriHalVersionColor spoof_color; } MomentumSettings; typedef struct { diff --git a/lib/momentum/settings.c b/lib/momentum/settings.c index 367fd51c90..0294a796f2 100644 --- a/lib/momentum/settings.c +++ b/lib/momentum/settings.c @@ -42,6 +42,7 @@ MomentumSettings momentum_settings = { .vgm_color_mode = VgmColorModeDefault, // Default .vgm_color_fg.value = 0x0000, // Default Black .vgm_color_bg.value = 0xFC00, // Default Orange + .spoof_color = FuriHalVersionColorUnknown, // Real }; typedef enum { @@ -112,6 +113,7 @@ static const struct { {setting_enum(vgm_color_mode, VgmColorModeCount)}, {setting_uint(vgm_color_fg, 0x0000, 0xFFFF)}, {setting_uint(vgm_color_bg, 0x0000, 0xFFFF)}, + {setting_enum(spoof_color, FuriHalVersionColorCount)}, }; void momentum_settings_load(void) { diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 3ad62f322a..ff41a9bc93 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -25,6 +25,7 @@ env.Append( File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), + File("protocols/felica/felica.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), @@ -35,6 +36,7 @@ env.Append( File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), + File("protocols/felica/felica_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), @@ -50,6 +52,7 @@ env.Append( File("helpers/iso14443_crc.h"), File("helpers/iso13239_crc.h"), File("helpers/nfc_data_generator.h"), + File("helpers/crypto1.h"), ], ) diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/helpers/crypto1.c similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.c rename to lib/nfc/helpers/crypto1.c diff --git a/lib/nfc/protocols/mf_classic/crypto1.h b/lib/nfc/helpers/crypto1.h similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.h rename to lib/nfc/helpers/crypto1.h diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 7de1310bf9..bc47e06422 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -13,6 +13,14 @@ static const uint32_t felica_data_format_version = 1; +/** @brief This is used in felica_prepare_first_block to define which + * type of block needs to be prepared. +*/ +typedef enum { + FelicaMACTypeRead, + FelicaMACTypeWrite, +} FelicaMACType; + const NfcDeviceBase nfc_device_felica = { .protocol_name = FELICA_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)felica_alloc, @@ -35,18 +43,18 @@ FelicaData* felica_alloc(void) { } void felica_free(FelicaData* data) { - furi_assert(data); - + furi_check(data); free(data); } void felica_reset(FelicaData* data) { + furi_check(data); memset(data, 0, sizeof(FelicaData)); } void felica_copy(FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); *data = *other; } @@ -59,7 +67,7 @@ bool felica_verify(FelicaData* data, const FuriString* device_type) { } bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { - furi_assert(data); + furi_check(data); bool parsed = false; @@ -77,13 +85,32 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; parsed = true; + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + parsed = false; + break; + } + } } while(false); return parsed; } bool felica_save(const FelicaData* data, FlipperFormat* ff) { - furi_assert(data); + furi_check(data); bool saved = false; @@ -98,15 +125,33 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + saved = true; + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + furi_string_free(temp_str); } while(false); return saved; } bool felica_is_equal(const FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); return memcmp(data, other, sizeof(FelicaData)) == 0; } @@ -119,7 +164,7 @@ const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType nam } const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID if(uid_len) { @@ -130,7 +175,7 @@ const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { } bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID const bool uid_valid = uid_len == FELICA_IDM_SIZE; @@ -145,3 +190,149 @@ FelicaData* felica_get_base_data(const FelicaData* data) { UNUSED(data); furi_crash("No base data"); } + +static void felica_reverse_copy_block(const uint8_t* array, uint8_t* reverse_array) { + furi_assert(array); + furi_assert(reverse_array); + + for(int i = 0; i < 8; i++) { + reverse_array[i] = array[7 - i]; + } +} + +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out) { + furi_check(ctx); + furi_check(ck); + furi_check(rc); + furi_check(out); + + uint8_t iv[8]; + memset(iv, 0, 8); + + uint8_t ck_reversed[16]; + felica_reverse_copy_block(ck, ck_reversed); + felica_reverse_copy_block(ck + 8, ck_reversed + 8); + + uint8_t rc_reversed[16]; + felica_reverse_copy_block(rc, rc_reversed); + felica_reverse_copy_block(rc + 8, rc_reversed + 8); + + mbedtls_des3_set2key_enc(ctx, ck_reversed); + mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, FELICA_DATA_BLOCK_SIZE, iv, rc_reversed, out); +} + +static bool felica_calculate_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* first_block, + const uint8_t* data, + const size_t length, + uint8_t* mac) { + furi_check((length % 8) == 0); + + uint8_t reverse_data[8]; + uint8_t iv[8]; + uint8_t out[8]; + mbedtls_des3_set2key_enc(ctx, session_key); + + felica_reverse_copy_block(rc, iv); + felica_reverse_copy_block(first_block, reverse_data); + uint8_t i = 0; + bool error = false; + do { + if(mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, 8, iv, reverse_data, out) == 0) { + memcpy(iv, out, sizeof(iv)); + felica_reverse_copy_block(data + i, reverse_data); + i += 8; + } else { + error = true; + break; + } + } while(i <= length); + + if(!error) { + felica_reverse_copy_block(out, mac); + } + return !error; +} + +static void felica_prepare_first_block( + FelicaMACType operation_type, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* out) { + furi_check(blocks); + furi_check(out); + if(operation_type == FelicaMACTypeRead) { + memset(out, 0xFF, 8); + for(uint8_t i = 0, j = 0; i < block_count; i++, j += 2) { + out[j] = blocks[i]; + out[j + 1] = 0; + } + } else { + furi_check(block_count == 4); + memset(out, 0, 8); + out[0] = blocks[0]; + out[1] = blocks[1]; + out[2] = blocks[2]; + out[4] = blocks[3]; + out[6] = FELICA_BLOCK_INDEX_MAC_A; + } +} + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(blocks); + furi_check(data); + + uint8_t first_block[8]; + uint8_t mac[8]; + felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); + + uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1); + felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac); + + uint8_t* mac_ptr = data + data_size_without_mac; + return !memcmp(mac, mac_ptr, 8); +} + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(wcnt); + furi_check(data); + furi_check(mac); + + const uint8_t WCNT_length = 3; + uint8_t block_data[WCNT_length + 1]; + uint8_t first_block[8]; + + memcpy(block_data, wcnt, WCNT_length); + block_data[3] = FELICA_BLOCK_INDEX_STATE; + felica_prepare_first_block(FelicaMACTypeWrite, block_data, WCNT_length + 1, first_block); + + uint8_t session_swapped[FELICA_DATA_BLOCK_SIZE]; + memcpy(session_swapped, session_key + 8, 8); + memcpy(session_swapped + 8, session_key, 8); + felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index e1820d4dc5..d032943d35 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -9,6 +10,23 @@ extern "C" { #define FELICA_IDM_SIZE (8U) #define FELICA_PMM_SIZE (8U) +#define FELICA_DATA_BLOCK_SIZE (16U) + +#define FELICA_BLOCKS_TOTAL_COUNT (27U) +#define FELICA_BLOCK_INDEX_REG (0x0EU) +#define FELICA_BLOCK_INDEX_RC (0x80U) +#define FELICA_BLOCK_INDEX_MAC (0x81U) +#define FELICA_BLOCK_INDEX_ID (0x82U) +#define FELICA_BLOCK_INDEX_D_ID (0x83U) +#define FELICA_BLOCK_INDEX_SER_C (0x84U) +#define FELICA_BLOCK_INDEX_SYS_C (0x85U) +#define FELICA_BLOCK_INDEX_CKV (0x86U) +#define FELICA_BLOCK_INDEX_CK (0x87U) +#define FELICA_BLOCK_INDEX_MC (0x88U) +#define FELICA_BLOCK_INDEX_WCNT (0x90U) +#define FELICA_BLOCK_INDEX_MAC_A (0x91U) +#define FELICA_BLOCK_INDEX_STATE (0x92U) +#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) @@ -23,6 +41,7 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +/** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, FelicaErrorNotPresent, @@ -35,17 +54,78 @@ typedef enum { FelicaErrorTimeout, } FelicaError; +/** @brief Separate type for card key block. Used in authentication process */ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaCardKey; + +/** @brief In Felica there two types of auth. Internal is the first one, after + * which external became possible. Here are two flags representing which one + * was passed */ +typedef struct { + bool internal : 1; + bool external : 1; +} FelicaAuthenticationStatus; + +/** @brief Struct which controls the process of authentication and can be passed as + * a parameter to the application level. In order to force user to fill card key block data. */ +typedef struct { + bool skip_auth; /**< By default it is true, so auth is skipped. By setting this to false several auth steps will be performed in order to pass auth*/ + FelicaCardKey + card_key; /**< User must fill this field with known card key in order to pass auth*/ + FelicaAuthenticationStatus auth_status; /**< Authentication status*/ +} FelicaAuthenticationContext; + +/** @brief Felica ID block */ typedef struct { uint8_t data[FELICA_IDM_SIZE]; } FelicaIDm; +/** @brief Felica PMm block */ typedef struct { uint8_t data[FELICA_PMM_SIZE]; } FelicaPMm; +/** @brief Felica block with status flags indicating last operation with it. + * See Felica manual for more details on status codes. */ +typedef struct { + uint8_t SF1; /**< Status flag 1, equals to 0 when success*/ + uint8_t SF2; /**< Status flag 2, equals to 0 when success*/ + uint8_t data[FELICA_DATA_BLOCK_SIZE]; /**< Block data */ +} FelicaBlock; + +/** @brief Felica filesystem structure */ +typedef struct { + FelicaBlock spad[14]; + FelicaBlock reg; + FelicaBlock rc; + FelicaBlock mac; + FelicaBlock id; + FelicaBlock d_id; + FelicaBlock ser_c; + FelicaBlock sys_c; + FelicaBlock ckv; + FelicaBlock ck; + FelicaBlock mc; + FelicaBlock wcnt; + FelicaBlock mac_a; + FelicaBlock state; + FelicaBlock crc_check; +} FelicaFileSystem; + +/** @brief Union which represents filesystem in junction with plain data dump */ +typedef union { + FelicaFileSystem fs; + uint8_t dump[sizeof(FelicaFileSystem)]; +} FelicaFSUnion; + +/** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; FelicaPMm pmm; + uint8_t blocks_total; + uint8_t blocks_read; + FelicaFSUnion data; } FelicaData; extern const NfcDeviceBase nfc_device_felica; @@ -74,6 +154,27 @@ bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); FelicaData* felica_get_base_data(const FelicaData* data); +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out); + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data); + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac); #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 23b1604e19..720daf2384 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -3,6 +3,11 @@ #include #include +#include + +#define TAG "FelicaPoller" + +typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { furi_assert(instance); @@ -23,6 +28,8 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + + mbedtls_des3_init(&instance->auth.des_context); instance->data = felica_alloc(); instance->felica_event.data = &instance->felica_event_data; @@ -40,6 +47,7 @@ static void felica_poller_free(FelicaPoller* instance) { furi_assert(instance->rx_buffer); furi_assert(instance->data); + mbedtls_des3_free(&instance->auth.des_context); bit_buffer_free(instance->tx_buffer); bit_buffer_free(instance->rx_buffer); felica_free(instance->data); @@ -55,6 +63,212 @@ static void instance->context = context; } +NfcCommand felica_poller_state_handler_idle(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Idle"); + felica_reset(instance->data); + instance->state = FelicaPollerStateActivated; + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Activate"); + + NfcCommand command = NfcCommandContinue; + + FelicaError error = felica_poller_activate(instance, instance->data); + if(error == FelicaErrorNone) { + furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + + instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; + instance->felica_event_data.auth_context = &instance->auth.context; + + instance->callback(instance->general_event, instance->context); + + bool skip_auth = instance->auth.context.skip_auth; + instance->state = skip_auth ? FelicaPollerStateReadBlocks : + FelicaPollerStateAuthenticateInternal; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth Internal"); + + felica_calculate_session_key( + &instance->auth.des_context, + instance->auth.context.card_key.data, + instance->data->data.fs.rc.data, + instance->auth.session_key.data); + + instance->state = FelicaPollerStateReadBlocks; + + uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; + FelicaPollerWriteCommandResponse* tx_resp; + do { + FelicaError error = felica_poller_write_blocks( + instance, 1, blocks, instance->data->data.fs.rc.data, &tx_resp); + if((error != FelicaErrorNone) || (tx_resp->SF1 != 0) || (tx_resp->SF2 != 0)) break; + + blocks[0] = FELICA_BLOCK_INDEX_ID; + blocks[1] = FELICA_BLOCK_INDEX_WCNT; + blocks[2] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + if(felica_check_mac( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + blocks, + rx_resp->block_count, + rx_resp->data)) { + instance->auth.context.auth_status.internal = true; + instance->data->data.fs.wcnt.SF1 = 0; + instance->data->data.fs.wcnt.SF2 = 0; + memcpy( + instance->data->data.fs.wcnt.data, + rx_resp->data + FELICA_DATA_BLOCK_SIZE, + FELICA_DATA_BLOCK_SIZE); + instance->state = FelicaPollerStateAuthenticateExternal; + } + } while(false); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth External"); + instance->state = FelicaPollerStateReadBlocks; + uint8_t blocks[2]; + + instance->data->data.fs.state.data[0] = 1; + FelicaAuthentication* auth = &instance->auth; + felica_calculate_mac_write( + &auth->des_context, + auth->session_key.data, + instance->data->data.fs.rc.data, + instance->data->data.fs.wcnt.data, + instance->data->data.fs.state.data, + instance->data->data.fs.mac_a.data); + + memcpy(instance->data->data.fs.mac_a.data + 8, instance->data->data.fs.wcnt.data, 3); //-V1086 + + uint8_t tx_data[FELICA_DATA_BLOCK_SIZE * 2]; + memcpy(tx_data, instance->data->data.fs.state.data, FELICA_DATA_BLOCK_SIZE); + memcpy( + tx_data + FELICA_DATA_BLOCK_SIZE, + instance->data->data.fs.mac_a.data, + FELICA_DATA_BLOCK_SIZE); + + do { + blocks[0] = FELICA_BLOCK_INDEX_STATE; + blocks[1] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerWriteCommandResponse* tx_resp; + FelicaError error = felica_poller_write_blocks(instance, 2, blocks, tx_data, &tx_resp); + if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; + + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + instance->data->data.fs.state.SF1 = 0; + instance->data->data.fs.state.SF2 = 0; + memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); + instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; + } while(false); + instance->state = FelicaPollerStateReadBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Blocks"); + + uint8_t block_count = 1; + uint8_t block_list[4] = {0, 0, 0, 0}; + block_list[0] = instance->block_index; + + instance->block_index++; + if(instance->block_index == FELICA_BLOCK_INDEX_REG + 1) { + instance->block_index = FELICA_BLOCK_INDEX_RC; + } else if(instance->block_index == FELICA_BLOCK_INDEX_MC + 1) { + instance->block_index = FELICA_BLOCK_INDEX_WCNT; + } else if(instance->block_index == FELICA_BLOCK_INDEX_STATE + 1) { + instance->block_index = FELICA_BLOCK_INDEX_CRC_CHECK; + } + + FelicaPollerReadCommandResponse* response; + FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + if(error == FelicaErrorNone) { + block_count = (response->SF1 == 0) ? response->block_count : block_count; + uint8_t* data_ptr = + instance->data->data.dump + instance->data->blocks_total * sizeof(FelicaBlock); + + *data_ptr++ = response->SF1; + *data_ptr++ = response->SF2; + + if(response->SF1 == 0) { + uint8_t* response_data_ptr = response->data; + instance->data->blocks_read++; + memcpy(data_ptr, response_data_ptr, FELICA_DATA_BLOCK_SIZE); + } else { + memset(data_ptr, 0, FELICA_DATA_BLOCK_SIZE); + } + instance->data->blocks_total++; + + if(instance->data->blocks_total == FELICA_BLOCKS_TOTAL_COUNT) { + instance->state = FelicaPollerStateReadSuccess; + } + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Success"); + + if(!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) { + instance->data->blocks_read--; + instance->felica_event.type = FelicaPollerEventTypeIncomplete; + } else { + memcpy( + instance->data->data.fs.ck.data, + instance->auth.context.card_key.data, + FELICA_DATA_BLOCK_SIZE); + instance->felica_event.type = FelicaPollerEventTypeReady; + } + + instance->felica_event_data.error = FelicaErrorNone; + return instance->callback(instance->general_event, instance->context); +} + +NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Fail"); + instance->callback(instance->general_event, instance->context); + + return NfcCommandStop; +} + +static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { + [FelicaPollerStateIdle] = felica_poller_state_handler_idle, + [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, + [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, + [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, + [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, +}; + static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -65,24 +279,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { NfcCommand command = NfcCommandContinue; if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_activate(instance, instance->data); - if(error == FelicaErrorNone) { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->felica_event.type = FelicaPollerEventTypeError; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = FelicaErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + command = felica_poller_handler[instance->state](instance); } return command; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b0e6778a0f..d4366e7672 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -19,14 +19,33 @@ typedef struct FelicaPoller FelicaPoller; */ typedef enum { FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ - FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< The card was activated and fully read by the poller. */ + FelicaPollerEventTypeIncomplete, /**< The card was activated and partly read by the poller. */ + FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */ } FelicaPollerEventType; +/** + * @brief Stucture for holding Felica session key which is calculated from rc and ck. +*/ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaSessionKey; + +/** + * @brief Structure used to hold authentication related fields. +*/ +typedef struct { + mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ + FelicaSessionKey session_key; /**< Calculated session key. */ + FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ +} FelicaAuthentication; + /** * @brief Felica poller event data. */ typedef union { FelicaError error; /**< Error code indicating card activation fail reason. */ + FelicaAuthenticationContext* auth_context; /**< Authentication context to be filled by user. */ } FelicaPollerEventData; /** diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index bfbf150ef9..f7726be32c 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -3,6 +3,11 @@ #include #define TAG "FelicaPoller" +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) + +#define FELICA_SERVICE_RW_ACCESS (0x0009U) +#define FELICA_SERVICE_RO_ACCESS (0x000BU) static FelicaError felica_poller_process_error(NfcError error) { switch(error) { @@ -15,8 +20,8 @@ static FelicaError felica_poller_process_error(NfcError error) { } } -static FelicaError felica_poller_frame_exchange( - FelicaPoller* instance, +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { @@ -93,10 +98,104 @@ FelicaError felica_poller_polling( return error; } -FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { +static void felica_poller_prepare_tx_buffer( + const FelicaPoller* instance, + const uint8_t command, + const uint16_t service_code, + const uint8_t block_count, + const uint8_t* const blocks, + const uint8_t data_block_count, + const uint8_t* data) { + FelicaCommandHeader cmd = { + .code = command, + .idm = instance->data->idm, + .service_num = 1, + .service_code = service_code, + .block_count = block_count, + }; + + FelicaBlockListElement block_list[4] = {{0}, {0}, {0}, {0}}; + for(uint8_t i = 0; i < block_count; i++) { + block_list[i].length = 1; + block_list[i].block_number = blocks[i]; + } + + uint8_t block_list_count = block_count; + uint8_t block_list_size = block_list_count * sizeof(FelicaBlockListElement); + uint8_t total_size = sizeof(FelicaCommandHeader) + 1 + block_list_size + + data_block_count * FELICA_DATA_BLOCK_SIZE; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, total_size); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeader)); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block_list, block_list_size); + + if(data_block_count != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, data, data_block_count * FELICA_DATA_BLOCK_SIZE); + } +} + +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); + furi_assert(block_count <= 4); + furi_assert(block_numbers); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_READ_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RO_ACCESS, + block_count, + block_numbers, + 0, + NULL); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = (FelicaPollerReadCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} + +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(block_count <= 2); + furi_assert(block_numbers); + furi_assert(data); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RW_ACCESS, + block_count, + block_numbers, + block_count, + data); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = + (FelicaPollerWriteCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} - felica_reset(data); +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { + furi_assert(instance); FelicaError ret; diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 3bd4d91f9f..f7df4c8450 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -1,7 +1,6 @@ #pragma once #include "felica_poller.h" - #include #ifdef __cplusplus @@ -18,11 +17,20 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateAuthenticateInternal, + FelicaPollerStateAuthenticateExternal, + FelicaPollerStateReadBlocks, + FelicaPollerStateReadSuccess, + FelicaPollerStateReadFailed, + + FelicaPollerStateNum } FelicaPollerState; struct FelicaPoller { Nfc* nfc; FelicaPollerState state; + FelicaAuthentication auth; + FelicaData* data; BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -31,6 +39,7 @@ struct FelicaPoller { FelicaPollerEvent felica_event; FelicaPollerEventData felica_event_data; NfcGenericCallback callback; + uint8_t block_index; void* context; }; @@ -46,13 +55,106 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef struct { + uint8_t service_code : 4; + uint8_t access_mode : 3; + uint8_t length : 1; + uint8_t block_number; +} FelicaBlockListElement; + +#pragma pack(push, 1) +typedef struct { + uint8_t code; + FelicaIDm idm; + uint8_t service_num; + uint16_t service_code; + uint8_t block_count; +} FelicaCommandHeader; +#pragma pack(pop) + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; + uint8_t block_count; + uint8_t data[]; +} FelicaPollerReadCommandResponse; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; +} FelicaPollerWriteCommandResponse; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); +/** + * @brief Performs felica polling operation as part of the activation process + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] cmd Pointer to polling command structure + * @param[out] resp Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure +*/ FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr); + +/** + * @brief Performs felica write operation with data provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in writing procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] data Data of blocks provided in block_numbers + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr); + +/** + * @brief Perform frame exchange procedure. + * + * Prepares data for sending by adding crc, after that performs + * low level calls to send package data to the card + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer with data to be transmitted + * @param[out] rx_buffer pointer to the buffer with received data from card + * @param[in] fwt timeout window + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 5269743b5c..af22b5234f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -3,7 +3,7 @@ #include "mf_classic_listener.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 19e5257017..518d029d07 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -315,6 +315,89 @@ MfClassicError mf_classic_poller_value_cmd( */ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Mifare Classic encrypted frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the plain data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with decyphered received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 2b01e74eed..949ef8e66e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -465,3 +465,77 @@ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8 return ret; } + +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = + iso14443_3a_poller_txrx(instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + MfClassicError ret = MfClassicErrorNone; + do { + crypto1_encrypt(instance->crypto, NULL, tx_buffer, instance->tx_encrypted_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + fwt_fc); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + + crypto1_decrypt(instance->crypto, instance->rx_encrypted_buffer, rx_buffer); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index a5af315307..14a7c61fd4 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,7 +3,7 @@ #include "mf_classic_poller.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4c30753860..2818e9a24e 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -21,8 +21,6 @@ extern "C" { #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) -#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) - #define MF_DESFIRE_MAX_KEYS (14) #define MF_DESFIRE_MAX_FILES (32) @@ -71,11 +69,6 @@ typedef struct { typedef uint8_t MfDesfireKeyVersion; -typedef struct { - MfDesfireKeySettings key_settings; - SimpleArray* key_versions; -} MfDesfireKeyConfiguration; - typedef enum { MfDesfireFileTypeStandard = 0, MfDesfireFileTypeBackup = 1, @@ -96,7 +89,8 @@ typedef uint16_t MfDesfireFileAccessRights; typedef struct { MfDesfireFileType type; MfDesfireFileCommunicationSettings comm; - MfDesfireFileAccessRights access_rights; + MfDesfireFileAccessRights access_rights[MF_DESFIRE_MAX_KEYS]; + uint8_t access_rights_len; union { struct { uint32_t size; @@ -136,6 +130,7 @@ typedef enum { MfDesfireErrorNotPresent, MfDesfireErrorProtocol, MfDesfireErrorTimeout, + MfDesfireErrorAuthentication, } MfDesfireError; typedef struct { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 646803e75b..bfbbadadff 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#define TAG "MfDesfire" + #define BITS_IN_BYTE (8U) #define MF_DESFIRE_FFF_VERSION_KEY \ @@ -175,59 +177,88 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer }; } MfDesfireFileSettingsLayout; + MfDesfireFileSettings file_settings_temp = {}; do { const size_t data_size = bit_buffer_get_size_bytes(buf); + const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); - const size_t max_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue); - - if(data_size < min_data_size) break; - if(data_size <= max_data_size) { - MfDesfireFileSettingsLayout layout; - bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); - - data->type = layout.header.type; - data->comm = layout.header.comm; - data->access_rights = layout.header.access_rights; - - if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { - if(data_size != min_data_size) break; - - data->data.size = layout.data.size; - } else if(data->type == MfDesfireFileTypeValue) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) - break; - data->value.lo_limit = layout.value.lo_limit; - data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.limited_credit_value; - data->value.limited_credit_enabled = layout.value.limited_credit_enabled; - - } else if( - data->type == MfDesfireFileTypeLinearRecord || - data->type == MfDesfireFileTypeCyclicRecord) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) - break; + if(data_size < min_data_size) { + FURI_LOG_E( + TAG, "File settings size %zu less than minimum %zu", data_size, min_data_size); + break; + } - data->record.size = layout.record.size; - data->record.max = layout.record.max; - data->record.cur = layout.record.cur; + size_t bytes_processed = sizeof(MfDesfireFileSettingsHeader); + MfDesfireFileSettingsLayout layout = {}; + memcpy(&layout.header, data_ptr, sizeof(MfDesfireFileSettingsHeader)); + bool has_additional_access_rights = (layout.header.comm & 0x80) != 0; + + file_settings_temp.type = layout.header.type; + file_settings_temp.comm = layout.header.comm & 0x03; + file_settings_temp.access_rights_len = 1; + file_settings_temp.access_rights[0] = layout.header.access_rights; + + if(file_settings_temp.type == MfDesfireFileTypeStandard || + file_settings_temp.type == MfDesfireFileTypeBackup) { + memcpy( + &layout.data, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsData)); + file_settings_temp.data.size = layout.data.size; + bytes_processed += sizeof(MfDesfireFileSettingsData); + } else if(file_settings_temp.type == MfDesfireFileTypeValue) { + memcpy( + &layout.value, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsValue)); + file_settings_temp.value.lo_limit = layout.value.lo_limit; + file_settings_temp.value.hi_limit = layout.value.hi_limit; + file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; + file_settings_temp.value.limited_credit_enabled = layout.value.limited_credit_enabled; + bytes_processed += sizeof(MfDesfireFileSettingsValue); + } else if( + file_settings_temp.type == MfDesfireFileTypeLinearRecord || + file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { + memcpy( + &layout.record, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsRecord)); + file_settings_temp.record.size = layout.record.size; + file_settings_temp.record.max = layout.record.max; + file_settings_temp.record.cur = layout.record.cur; + bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else { + FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); + break; + } - } else { + if(has_additional_access_rights) { + uint8_t additional_access_rights_len = bit_buffer_get_byte(buf, bytes_processed); + FURI_LOG_D(TAG, "Has additional rights: %d", additional_access_rights_len); + if(data_size != bytes_processed + + additional_access_rights_len * sizeof(MfDesfireFileAccessRights) + + 1) { + FURI_LOG_W(TAG, "Unexpected command length: %zu", data_size); + for(size_t i = 0; i < bit_buffer_get_size_bytes(buf); i++) { + printf("%02X ", bit_buffer_get_byte(buf, i)); + } + printf("\r\n"); break; } - } else { - // TODO FL-3750: process HID Desfire command response here - // Set default fields for now - data->type = 0; - data->comm = 0; - data->access_rights = 0; - data->data.size = 0; + if(additional_access_rights_len > + MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights)) + break; + + memcpy( + &file_settings_temp.access_rights[1], + &data_ptr[bytes_processed], + additional_access_rights_len * sizeof(MfDesfireFileAccessRights)); + file_settings_temp.access_rights_len += additional_access_rights_len; } + *data = file_settings_temp; parsed = true; } while(false); @@ -392,18 +423,19 @@ bool mf_desfire_file_settings_load( break; furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + uint32_t access_rights_len = 0; + if(!flipper_format_get_value_count(ff, furi_string_get_cstr(key), &access_rights_len)) + break; + if((access_rights_len == 0) || ((access_rights_len % 2) != 0)) break; if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(key), - (uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + ff, furi_string_get_cstr(key), (uint8_t*)&data->access_rights, access_rights_len)) break; + data->access_rights_len = access_rights_len / sizeof(MfDesfireFileAccessRights); if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) break; - } else if(data->type == MfDesfireFileTypeValue) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) @@ -641,8 +673,8 @@ bool mf_desfire_file_settings_save( if(!flipper_format_write_hex( ff, furi_string_get_cstr(key), - (const uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + (const uint8_t*)data->access_rights, + data->access_rights_len * sizeof(MfDesfireFileAccessRights))) break; if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { @@ -737,8 +769,11 @@ bool mf_desfire_application_save( if(i != key_version_count) break; const uint32_t file_count = simple_array_get_count(data->file_ids); - if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) - break; + if(file_count > 0) { + if(!mf_desfire_file_ids_save( + simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + } for(i = 0; i < file_count; ++i) { const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 05381096d1..21250baace 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -5,6 +5,52 @@ #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" +// Successful operation +#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Current authentication status does not allow the requested command +#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) + // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c9d8bbab60..fa8a7ae9b9 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -75,17 +75,23 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else if(instance->error == MfDesfireErrorNotPresent) { + FURI_LOG_D(TAG, "Read free memoty is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + command = NfcCommandReset; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = MfDesfirePollerStateReadFailed; } - return NfcCommandContinue; + return command; } static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { @@ -94,6 +100,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + instance->data->master_key_settings.is_free_directory_list = false; + instance->data->master_key_settings.max_keys = 1; + instance->state = MfDesfirePollerStateReadMasterKeyVersion; } else { FURI_LOG_E(TAG, "Failed to read master key settings"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -110,7 +121,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol instance->data->master_key_settings.max_keys); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key version success"); - instance->state = MfDesfirePollerStateReadApplicationIds; + if(instance->data->master_key_settings.is_free_directory_list) { + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + instance->state = MfDesfirePollerStateReadSuccess; + } } else { FURI_LOG_E(TAG, "Failed to read master key version"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -126,6 +141,9 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Read application ids impossible without authentication"); + instance->state = MfDesfirePollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read application ids"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 790f117157..2b86318491 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -19,6 +19,17 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { } } +MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { + switch(status_code) { + case MF_DESFIRE_STATUS_OPERATION_OK: + return MfDesfireErrorNone; + case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + return MfDesfireErrorAuthentication; + default: + return MfDesfireErrorProtocol; + } +} + MfDesfireError mf_desfire_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, @@ -42,7 +53,7 @@ MfDesfireError mf_desfire_send_chunks( } bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); @@ -50,7 +61,8 @@ MfDesfireError mf_desfire_send_chunks( bit_buffer_reset(rx_buffer); } - while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + while( + bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); @@ -71,6 +83,11 @@ MfDesfireError mf_desfire_send_chunks( } } while(false); + if(error == MfDesfireErrorNone) { + uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); + error = mf_desfire_process_status_code(err_code); + } + return error; } @@ -110,7 +127,7 @@ MfDesfireError if(error != MfDesfireErrorNone) break; if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; + error = MfDesfireErrorNotPresent; } } while(false); @@ -414,13 +431,25 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( simple_array_init(data, file_id_count); } - for(uint32_t i = 0; i < file_id_count; ++i) { + for(size_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); const MfDesfireFileType file_type = file_settings_cur->type; MfDesfireFileData* file_data = simple_array_get(data, i); + bool can_read_data = false; + for(size_t j = 0; j < file_settings_cur->access_rights_len; j++) { + uint8_t read_access = (file_settings_cur->access_rights[j] >> 12) & 0x0f; + uint8_t read_write_access = (file_settings_cur->access_rights[j] >> 4) & 0x0f; + can_read_data = (read_access == 0x0e) || (read_write_access == 0x0e); + if(can_read_data) break; + } + if(!can_read_data) { + FURI_LOG_D(TAG, "Can't read file %zu data without authentication", i); + continue; + } + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); @@ -432,8 +461,6 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } - - if(error != MfDesfireErrorNone) break; } return error; @@ -448,22 +475,36 @@ MfDesfireError do { error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); + if(error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + data->key_settings.is_free_directory_list = false; + error = MfDesfireErrorNone; + break; + } if(error != MfDesfireErrorNone) break; error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read key version: %d", error); + break; + } error = mf_desfire_poller_read_file_ids(instance, data->file_ids); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file ids: %d", error); + break; + } error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file settings: %d", error); + break; + } error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); - if(error != MfDesfireErrorNone) break; } while(false); @@ -484,11 +525,13 @@ MfDesfireError mf_desfire_poller_read_applications( simple_array_init(data, app_id_count); } - for(uint32_t i = 0; i < app_id_count; ++i) { + for(size_t i = 0; i < app_id_count; ++i) { do { + FURI_LOG_D(TAG, "Selecting app %zu", i); error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; + FURI_LOG_D(TAG, "Reading app %zu", i); MfDesfireApplication* current_app = simple_array_get(data, i); error = mf_desfire_poller_read_application(instance, current_app); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 1c80af36fb..19e38bebbc 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -47,6 +47,8 @@ struct MfDesfirePoller { MfDesfireError mf_desfire_process_error(Iso14443_4aError error); +MfDesfireError mf_desfire_process_status_code(uint8_t status_code); + const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); #ifdef __cplusplus diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c index 729ccca807..25e5223439 100644 --- a/lib/subghz/devices/devices.c +++ b/lib/subghz/devices/devices.c @@ -35,7 +35,7 @@ bool subghz_devices_begin(const SubGhzDevice* device) { SubGhzDeviceConf conf = { .ver = 1, .extended_range = false, // TODO - .power_amp = furi_hal_subghz_get_ext_power_amp(), + .power_amp = true, }; ret = device->interconnect->begin(&conf); diff --git a/lib/tlsf b/lib/tlsf new file mode 160000 index 0000000000..8fc595fe22 --- /dev/null +++ b/lib/tlsf @@ -0,0 +1 @@ +Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 diff --git a/lib/tlsf.scons b/lib/tlsf.scons new file mode 100644 index 0000000000..0a8419dbdc --- /dev/null +++ b/lib/tlsf.scons @@ -0,0 +1,21 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/tlsf", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="tlsf") +libenv.ApplyLibFlags() + +libenv.Append( + CPPDEFINES=[], +) + +sources = [File("tlsf/tlsf.c")] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index ad7c08668c..ea6213d986 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -74,22 +74,29 @@ void name_generator_make_random_prefixed( uint8_t name_generator_left_i = rand() % COUNT_OF(name_generator_left); uint8_t name_generator_right_i = rand() % COUNT_OF(name_generator_right); - if(prefix_after) { - snprintf( - name, - max_name_size, - "%s-%s%s%s", - name_generator_left[name_generator_left_i], - name_generator_right[name_generator_right_i], - prefix ? "_" : "", - prefix ? prefix : ""); + if(prefix) { + if(prefix_after) { + snprintf( + name, + max_name_size, + "%s-%s_%s", + name_generator_left[name_generator_left_i], + name_generator_right[name_generator_right_i], + prefix); + } else { + snprintf( + name, + max_name_size, + "%s_%s-%s", + prefix, + name_generator_left[name_generator_left_i], + name_generator_right[name_generator_right_i]); + } } else { snprintf( name, max_name_size, - "%s%s%s-%s", - prefix ? prefix : "", - prefix ? "_" : "", + "%s-%s", name_generator_left[name_generator_left_i], name_generator_right[name_generator_right_i]); } @@ -111,7 +118,6 @@ void name_generator_make_detailed_datetime( bool prefix_after) { furi_check(name); furi_check(max_name_size); - furi_check(prefix); DateTime dateTime; if(custom_time) { @@ -120,24 +126,37 @@ void name_generator_make_detailed_datetime( furi_hal_rtc_get_datetime(&dateTime); } - if(prefix_after) { - snprintf( - name, - max_name_size, - "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d_%s", - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - prefix); + if(prefix) { + if(prefix_after) { + snprintf( + name, + max_name_size, + "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d_%s", + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + prefix); + } else { + snprintf( + name, + max_name_size, + "%s_%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", + prefix, + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second); + } } else { snprintf( name, max_name_size, - "%s_%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", - prefix, + "%.4d-%.2d-%.2d_%.2d,%.2d,%.2d", dateTime.year, dateTime.month, dateTime.day, diff --git a/lib/toolbox/protocols/protocol.h b/lib/toolbox/protocols/protocol.h index 5a8015b1e1..0ee165d13e 100644 --- a/lib/toolbox/protocols/protocol.h +++ b/lib/toolbox/protocols/protocol.h @@ -40,6 +40,7 @@ typedef struct { ProtocolGetData get_data; ProtocolDecoder decoder; ProtocolEncoder encoder; + ProtocolRenderData render_uid; ProtocolRenderData render_data; ProtocolRenderData render_brief_data; ProtocolWriteData write_data; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d1..b5eaa47028 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,61.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -199,6 +199,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -514,9 +515,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1086,6 +1085,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, @@ -1748,6 +1757,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, @@ -1972,7 +1982,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1980,8 +1991,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6e0a254c13..6cfc939b8c 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 9935d4124c..45ca3e6c49 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 // 2nd column // 5V: 19 - {.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, - {.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, - {.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, - {.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, - {.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, - {.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, + {.pin = &gpio_ext_pc5, + .name = "PC5", + .channel = FuriHalAdcChannel14, + .number = 20, + .debug = false}, + {.pin = &gpio_ext_pc4, + .name = "PC4", + .channel = FuriHalAdcChannel13, + .number = 21, + .debug = false}, + {.pin = &gpio_ext_pa5, + .name = "PA5", + .channel = FuriHalAdcChannel10, + .number = 22, + .debug = false}, + {.pin = &gpio_ext_pb9, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 23, + .debug = false}, + {.pin = &gpio_ext_pa0, + .name = "PA0", + .channel = FuriHalAdcChannel5, + .number = 24, + .debug = false}, + {.pin = &gpio_ext_pa1, + .name = "PA1", + .channel = FuriHalAdcChannel6, + .number = 25, + .debug = false}, // KEY: 26 // Space // 3v3: 27 - {.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false}, + {.pin = &gpio_ext_pa15, + .name = "PA15", + .channel = FuriHalAdcChannelNone, + .number = 28, + .debug = false}, // GND: 29 - {.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, - {.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, - {.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, - {.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, - {.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, - {.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, + {.pin = &gpio_ext_pe4, + .name = "PE4", + .channel = FuriHalAdcChannelNone, + .number = 30, + .debug = false}, + {.pin = &gpio_ext_pa2, + .name = "PA2", + .channel = FuriHalAdcChannel7, + .number = 31, + .debug = false}, + {.pin = &gpio_ext_pb4, + .name = "PB4", + .channel = FuriHalAdcChannelNone, + .number = 32, + .debug = false}, + {.pin = &gpio_ext_pb5, + .name = "PB5", + .channel = FuriHalAdcChannelNone, + .number = 33, + .debug = false}, + {.pin = &gpio_ext_pd0, + .name = "PD0", + .channel = FuriHalAdcChannelNone, + .number = 34, + .debug = false}, + {.pin = &gpio_ext_pb13, + .name = "PB13", + .channel = FuriHalAdcChannelNone, + .number = 35, + .debug = false}, // GND: 36 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 3d45ad885e..8f6173eb9d 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd4..a61c1373e1 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,6 +13,7 @@ "print", "flipper18", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -68,4 +69,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index be6c7fc92c..1fb2632b2d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,61.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/main/archive/helpers/archive_helpers_ext.h,, Header,+,applications/main/subghz/subghz_fap.h,, @@ -136,6 +136,7 @@ Header,+,lib/music_worker/music_worker.h,, Header,+,lib/nanopb/pb.h,, Header,+,lib/nanopb/pb_decode.h,, Header,+,lib/nanopb/pb_encode.h,, +Header,+,lib/nfc/helpers/crypto1.h,, Header,+,lib/nfc/helpers/iso13239_crc.h,, Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, @@ -147,6 +148,8 @@ Header,+,lib/nfc/nfc_poller.h,, Header,+,lib/nfc/nfc_scanner.h,, Header,+,lib/nfc/protocols/emv/emv.h,, Header,+,lib/nfc/protocols/emv/emv_poller.h,, +Header,+,lib/nfc/protocols/felica/felica.h,, +Header,+,lib/nfc/protocols/felica/felica_poller.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -282,6 +285,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -609,9 +613,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -888,6 +890,16 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,+,crypto1_alloc,Crypto1*, +Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" +Function,+,crypto1_free,void,Crypto1* +Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_reset,void,Crypto1* +Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* @@ -1055,6 +1067,22 @@ Function,-,fdim,double,"double, double" Function,-,fdimf,float,"float, float" Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" +Function,+,felica_alloc,FelicaData*, +Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" +Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_free,void,FelicaData* +Function,+,felica_get_base_data,FelicaData*,const FelicaData* +Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" +Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" +Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_reset,void,FelicaData* +Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" +Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* @@ -1236,6 +1264,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, @@ -1643,7 +1681,6 @@ Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, -Function,+,furi_hal_subghz_get_ext_power_amp,_Bool, Function,+,furi_hal_subghz_get_lqi,uint8_t, Function,+,furi_hal_subghz_get_rolling_counter_mult,int8_t, Function,+,furi_hal_subghz_get_rssi,float, @@ -1661,7 +1698,6 @@ Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* -Function,+,furi_hal_subghz_set_ext_power_amp,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath @@ -1956,6 +1992,7 @@ Function,+,ibutton_protocols_read,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_render_brief_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_error,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_render_uid,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_save,_Bool,"iButtonProtocols*, const iButtonKey*, const char*" Function,+,ibutton_protocols_write_blank,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_write_copy,_Bool,"iButtonProtocols*, iButtonKey*" @@ -2458,7 +2495,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2466,8 +2504,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" @@ -2511,6 +2548,10 @@ Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, M Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_encrypted_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_standard_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" @@ -2900,6 +2941,7 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." +Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,process_favorite_launch,_Bool,char** Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" @@ -3766,6 +3808,7 @@ Variable,+,I_DolphinMafia_119x62,Icon, Variable,+,I_DolphinReadingSuccess_59x63,Icon, Variable,+,I_DolphinSaved_92x58,Icon, Variable,+,I_DolphinSuccess_91x55,Icon, +Variable,+,I_DolphinWait_59x54,Icon, Variable,+,I_DolphinWait_61x59,Icon, Variable,+,I_Drive_112x35,Icon, Variable,+,I_Dynamic_9x7,Icon, @@ -4187,6 +4230,7 @@ Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, Variable,+,momentum_settings,MomentumSettings, Variable,-,nfc_device_emv,const NfcDeviceBase, +Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, diff --git a/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h index fbf6d0291d..d0e089eb1e 100644 --- a/targets/f7/ble_glue/app_conf.h +++ b/targets/f7/ble_glue/app_conf.h @@ -9,7 +9,6 @@ /** * Define IO Authentication */ -#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN #define CFG_ENCRYPTION_KEY_SIZE_MAX (16) #define CFG_ENCRYPTION_KEY_SIZE_MIN (8) @@ -18,11 +17,6 @@ */ #define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO -/** - * Define MITM modes - */ -#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED - /** * Define Secure Connections Support */ diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c index 2ef3e056fb..60338cb76b 100644 --- a/targets/f7/ble_glue/extra_beacon.c +++ b/targets/f7/ble_glue/extra_beacon.c @@ -9,7 +9,8 @@ #define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) // Also used as an indicator of whether the beacon had ever been configured -#define GAP_MIN_ADV_INTERVAL_MS (20) +// AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms +#define GAP_MIN_ADV_INTERVAL_MS (30u) typedef struct { GapExtraBeaconConfig last_config; diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 1d535664e3..5a95cff263 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -372,9 +372,8 @@ static void gap_init_svc(Gap* gap) { // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability - bool bonding_mode = gap->config->bonding_mode; - uint8_t cfg_mitm_protection = CFG_MITM_PROTECTION; - uint8_t cfg_used_fixed_pin = CFG_USED_FIXED_PIN; + uint8_t auth_req_mitm_mode = MITM_PROTECTION_REQUIRED; + uint8_t auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN; bool keypress_supported = false; if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); @@ -383,22 +382,21 @@ static void gap_init_svc(Gap* gap) { keypress_supported = true; } else if(gap->config->pairing_method == GapPairingNone) { // "Just works" pairing method (iOS accepts it, it seems Android and Linux don't) - bonding_mode = false; - cfg_mitm_protection = MITM_PROTECTION_NOT_REQUIRED; - cfg_used_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; + auth_req_mitm_mode = MITM_PROTECTION_NOT_REQUIRED; + auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; // If "just works" isn't supported, we want the numeric comparaison method aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( - bonding_mode, - cfg_mitm_protection, + gap->config->bonding_mode, + auth_req_mitm_mode, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - cfg_used_fixed_pin, + auth_req_use_fixed_pin, 0, CFG_IDENTITY_ADDRESS); // Configure whitelist diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 8ee4b3d91c..4376570ad7 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -52,6 +52,7 @@ typedef enum { GapPairingNone, GapPairingPinCodeShow, GapPairingPinCodeVerifyYesNo, + GapPairingCount, } GapPairing; typedef struct { diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index a3949abfc8..165b813307 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -46,8 +46,8 @@ static GapConfig serial_template_config = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }}; diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index 319dd21732..df86cb7f15 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_alloc_from_pool(sizeof(SectorCache)); + cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); } if(cache != NULL) { diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 425865e82d..d4b7a9e319 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -41,6 +41,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f7/furi_hal/furi_hal_adc.c b/targets/f7/furi_hal/furi_hal_adc.c new file mode 100644 index 0000000000..7a15f2e54b --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_adc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include + +#include + +#include +#include + +struct FuriHalAdcHandle { + ADC_TypeDef* adc; + FuriMutex* mutex; + uint32_t full_scale; +}; + +static const uint32_t furi_hal_adc_clock[] = { + [FuriHalAdcClockSync16] = LL_ADC_CLOCK_SYNC_PCLK_DIV4, + [FuriHalAdcClockSync32] = LL_ADC_CLOCK_SYNC_PCLK_DIV2, + [FuriHalAdcClockSync64] = LL_ADC_CLOCK_SYNC_PCLK_DIV1, +}; + +static const uint8_t furi_hal_adc_clock_div[] = { + [FuriHalAdcClockSync16] = 4, + [FuriHalAdcClockSync32] = 2, + [FuriHalAdcClockSync64] = 1, +}; + +static const uint32_t furi_hal_adc_oversample_ratio[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_RATIO_2, + [FuriHalAdcOversample4] = LL_ADC_OVS_RATIO_4, + [FuriHalAdcOversample8] = LL_ADC_OVS_RATIO_8, + [FuriHalAdcOversample16] = LL_ADC_OVS_RATIO_16, + [FuriHalAdcOversample32] = LL_ADC_OVS_RATIO_32, + [FuriHalAdcOversample64] = LL_ADC_OVS_RATIO_64, + [FuriHalAdcOversample128] = LL_ADC_OVS_RATIO_128, + [FuriHalAdcOversample256] = LL_ADC_OVS_RATIO_256, +}; + +static const uint32_t furi_hal_adc_oversample_shift[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_SHIFT_RIGHT_1, + [FuriHalAdcOversample4] = LL_ADC_OVS_SHIFT_RIGHT_2, + [FuriHalAdcOversample8] = LL_ADC_OVS_SHIFT_RIGHT_3, + [FuriHalAdcOversample16] = LL_ADC_OVS_SHIFT_RIGHT_4, + [FuriHalAdcOversample32] = LL_ADC_OVS_SHIFT_RIGHT_5, + [FuriHalAdcOversample64] = LL_ADC_OVS_SHIFT_RIGHT_6, + [FuriHalAdcOversample128] = LL_ADC_OVS_SHIFT_RIGHT_7, + [FuriHalAdcOversample256] = LL_ADC_OVS_SHIFT_RIGHT_8, +}; + +static const uint32_t furi_hal_adc_sampling_time[] = { + [FuriHalAdcSamplingtime2_5] = LL_ADC_SAMPLINGTIME_2CYCLES_5, + [FuriHalAdcSamplingtime6_5] = LL_ADC_SAMPLINGTIME_6CYCLES_5, + [FuriHalAdcSamplingtime12_5] = LL_ADC_SAMPLINGTIME_12CYCLES_5, + [FuriHalAdcSamplingtime24_5] = LL_ADC_SAMPLINGTIME_24CYCLES_5, + [FuriHalAdcSamplingtime47_5] = LL_ADC_SAMPLINGTIME_47CYCLES_5, + [FuriHalAdcSamplingtime92_5] = LL_ADC_SAMPLINGTIME_92CYCLES_5, + [FuriHalAdcSamplingtime247_5] = LL_ADC_SAMPLINGTIME_247CYCLES_5, + [FuriHalAdcSamplingtime640_5] = LL_ADC_SAMPLINGTIME_640CYCLES_5, +}; + +static const uint32_t furi_hal_adc_channel_map[] = { + [FuriHalAdcChannel0] = LL_ADC_CHANNEL_0, + [FuriHalAdcChannel1] = LL_ADC_CHANNEL_1, + [FuriHalAdcChannel2] = LL_ADC_CHANNEL_2, + [FuriHalAdcChannel3] = LL_ADC_CHANNEL_3, + [FuriHalAdcChannel4] = LL_ADC_CHANNEL_4, + [FuriHalAdcChannel5] = LL_ADC_CHANNEL_5, + [FuriHalAdcChannel6] = LL_ADC_CHANNEL_6, + [FuriHalAdcChannel7] = LL_ADC_CHANNEL_7, + [FuriHalAdcChannel8] = LL_ADC_CHANNEL_8, + [FuriHalAdcChannel9] = LL_ADC_CHANNEL_9, + [FuriHalAdcChannel10] = LL_ADC_CHANNEL_10, + [FuriHalAdcChannel11] = LL_ADC_CHANNEL_11, + [FuriHalAdcChannel12] = LL_ADC_CHANNEL_12, + [FuriHalAdcChannel13] = LL_ADC_CHANNEL_13, + [FuriHalAdcChannel14] = LL_ADC_CHANNEL_14, + [FuriHalAdcChannel15] = LL_ADC_CHANNEL_15, + [FuriHalAdcChannel16] = LL_ADC_CHANNEL_16, + [FuriHalAdcChannel17] = LL_ADC_CHANNEL_17, + [FuriHalAdcChannel18] = LL_ADC_CHANNEL_18, + [FuriHalAdcChannelVREFINT] = LL_ADC_CHANNEL_VREFINT, + [FuriHalAdcChannelTEMPSENSOR] = LL_ADC_CHANNEL_TEMPSENSOR, + [FuriHalAdcChannelVBAT] = LL_ADC_CHANNEL_VBAT, +}; + +static FuriHalAdcHandle* furi_hal_adc_handle = NULL; + +void furi_hal_adc_init(void) { + furi_hal_adc_handle = malloc(sizeof(FuriHalAdcHandle)); + furi_hal_adc_handle->adc = ADC1; + furi_hal_adc_handle->mutex = furi_mutex_alloc(FuriMutexTypeNormal); +} + +FuriHalAdcHandle* furi_hal_adc_acquire(void) { + furi_check(furi_mutex_acquire(furi_hal_adc_handle->mutex, FuriWaitForever) == FuriStatusOk); + + furi_hal_power_insomnia_enter(); + + furi_hal_bus_enable(FuriHalBusADC); + + return furi_hal_adc_handle; +} + +void furi_hal_adc_release(FuriHalAdcHandle* handle) { + furi_check(handle); + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + LL_VREFBUF_Disable(); + LL_VREFBUF_EnableHIZ(); + + furi_hal_power_insomnia_exit(); + + furi_check(furi_mutex_release(furi_hal_adc_handle->mutex) == FuriStatusOk); +} + +void furi_hal_adc_configure(FuriHalAdcHandle* handle) { + furi_hal_adc_configure_ex( + handle, + FuriHalAdcScale2048, + FuriHalAdcClockSync64, + FuriHalAdcOversample64, + FuriHalAdcSamplingtime247_5); +} + +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time) { + furi_check(handle); + furi_check(scale == FuriHalAdcScale2048 || scale == FuriHalAdcScale2500); + furi_check(clock <= FuriHalAdcClockSync64); + furi_check(oversample <= FuriHalAdcOversampleNone); + furi_check(sampling_time <= FuriHalAdcSamplingtime640_5); + + FuriHalCortexTimer timer; + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + uint32_t trim_value = 0; + switch(scale) { + case FuriHalAdcScale2048: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE0); + trim_value = LL_VREFBUF_SC0_GetCalibration() & 0x3FU; + handle->full_scale = 2048; + break; + case FuriHalAdcScale2500: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1); + trim_value = LL_VREFBUF_SC1_GetCalibration() & 0x3FU; + handle->full_scale = 2500; + break; + default: + furi_crash(); + } + LL_VREFBUF_SetTrimming(trim_value); + LL_VREFBUF_Enable(); + LL_VREFBUF_DisableHIZ(); + + timer = furi_hal_cortex_timer_get(500000); // 500ms to stabilize VREF + while(!LL_VREFBUF_IsVREFReady()) { + furi_check(!furi_hal_cortex_timer_is_expired(timer), "VREF fail"); + }; + + furi_hal_bus_enable(FuriHalBusADC); + + // ADC Common config + LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0}; + ADC_CommonInitStruct.CommonClock = furi_hal_adc_clock[clock]; + furi_check( + LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(handle->adc), &ADC_CommonInitStruct) == + SUCCESS); + LL_ADC_SetCommonPathInternalCh( + __LL_ADC_COMMON_INSTANCE(handle->adc), + LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR | + LL_ADC_PATH_INTERNAL_VBAT); + + // ADC config part 1 + LL_ADC_InitTypeDef ADC_InitStruct = {0}; + ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //-V1048 + ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; + ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; + furi_check(LL_ADC_Init(handle->adc, &ADC_InitStruct) == SUCCESS); + + // ADC config part 2: groups parameters + LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0}; + ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //-V1048 + ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE; + ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; + ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; + ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE; + ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; + furi_check(LL_ADC_REG_Init(handle->adc, &ADC_REG_InitStruct) == SUCCESS); + + // ADC config part 3: sequencer and channels + if(oversample == FuriHalAdcOversampleNone) { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_DISABLE); + } else { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_GRP_REGULAR_CONTINUED); + LL_ADC_ConfigOverSamplingRatioShift( + handle->adc, + furi_hal_adc_oversample_ratio[oversample], + furi_hal_adc_oversample_shift[oversample]); + } + + for(FuriHalAdcChannel channel = FuriHalAdcChannel0; channel < FuriHalAdcChannelNone; + channel++) { + // 47.5 cycles on 64MHz is first meaningful value for internal sources sampling + LL_ADC_SetChannelSamplingTime( + handle->adc, + furi_hal_adc_channel_map[channel], + furi_hal_adc_sampling_time[sampling_time]); + LL_ADC_SetChannelSingleDiff( + handle->adc, furi_hal_adc_channel_map[channel], LL_ADC_SINGLE_ENDED); + } + + // Disable ADC deep power down (enabled by default after reset state) + LL_ADC_DisableDeepPowerDown(handle->adc); + + // Enable ADC internal voltage regulator + LL_ADC_EnableInternalRegulator(handle->adc); + // Delay for ADC internal voltage regulator stabilization. + timer = furi_hal_cortex_timer_get(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US); + while(!furi_hal_cortex_timer_is_expired(timer)) + ; + + // Run ADC self calibration + LL_ADC_StartCalibration(handle->adc, LL_ADC_SINGLE_ENDED); + // Poll for ADC effectively calibrated + while(LL_ADC_IsCalibrationOnGoing(handle->adc) != 0) + ; + // Delay between ADC end of calibration and ADC enable + size_t end = + DWT->CYCCNT + (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * furi_hal_adc_clock_div[clock]); + while(DWT->CYCCNT < end) + ; + + // Enable ADC + LL_ADC_ClearFlag_ADRDY(handle->adc); + LL_ADC_Enable(handle->adc); + while(LL_ADC_IsActiveFlag_ADRDY(handle->adc) == 0) + ; +} + +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel) { + furi_check(handle); + furi_check(channel <= FuriHalAdcChannelVBAT); + furi_check(LL_ADC_IsEnabled(handle->adc) == 1); + furi_check(LL_ADC_IsDisableOngoing(handle->adc) == 0); + furi_check(LL_ADC_REG_IsConversionOngoing(handle->adc) == 0); + + LL_ADC_REG_SetSequencerRanks( + handle->adc, LL_ADC_REG_RANK_1, furi_hal_adc_channel_map[channel]); + + LL_ADC_REG_StartConversion(handle->adc); + + while(LL_ADC_IsActiveFlag_EOC(handle->adc) == 0) + ; + uint16_t value = LL_ADC_REG_ReadConversionData12(handle->adc); + + return value; +} + +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_DATA_TO_VOLTAGE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value) { + UNUSED(handle); + return (float)__LL_ADC_CALC_VREFANALOG_VOLTAGE(value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_TEMPERATURE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value) { + return furi_hal_adc_convert_to_voltage(handle, value) * 3; +} diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index ad21031fe6..9184fa7157 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -121,6 +121,7 @@ void furi_hal_clock_init(void) { LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); + LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK); FURI_LOG_I(TAG, "Init OK"); } diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 6f2210cc1d..a0b166fad4 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -357,7 +357,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc if(infrared_tx_output == FuriHalInfraredTxPinInternal) { LL_TIM_OC_SetCompareCH3( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -368,7 +368,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc } else if(infrared_tx_output == FuriHalInfraredTxPinExtPA7) { LL_TIM_OC_SetCompareCH1( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -609,7 +609,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { } void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { - if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || + if((duty_cycle > 1.0f) || (duty_cycle <= 0.0f) || (freq > INFRARED_MAX_FREQUENCY) || (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { furi_crash(); } diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index b98d93769d..486c24230e 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, - {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_infrared_tx, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index bb01b9bcdd..99768654d9 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; @@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void); void furi_hal_resources_init(void); -/** - * Get a corresponding external connector pin number for a gpio - * @param gpio GpioPin - * @return pin number or -1 if gpio is not on the external connector +/** Get a corresponding external connector pin number for a gpio + * + * @param gpio GpioPin + * + * @return pin number or -1 if gpio is not on the external connector */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index 15f2ad01ac..1350bcd584 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -52,7 +52,6 @@ typedef struct { const GpioPin* async_mirror_pin; int8_t rolling_counter_mult; - bool ext_power_amp : 1; bool extended_frequency_i : 1; bool bypass_region : 1; } FuriHalSubGhz; @@ -62,7 +61,6 @@ volatile FuriHalSubGhz furi_hal_subghz = { .regulation = SubGhzRegulationTxRx, .async_mirror_pin = NULL, .rolling_counter_mult = 1, - .ext_power_amp = false, .extended_frequency_i = false, .bypass_region = false, }; @@ -83,14 +81,6 @@ void furi_hal_subghz_set_bypass_region(bool enabled) { furi_hal_subghz.bypass_region = enabled; } -void furi_hal_subghz_set_ext_power_amp(bool enabled) { - furi_hal_subghz.ext_power_amp = enabled; -} - -bool furi_hal_subghz_get_ext_power_amp(void) { - return furi_hal_subghz.ext_power_amp; -} - void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } diff --git a/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h index 68c040928a..10d89947a9 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -238,54 +238,7 @@ bool furi_hal_subghz_is_async_tx_complete(void); */ void furi_hal_subghz_stop_async_tx(void); -// /** Initialize and switch to power save mode Used by internal API-HAL -// * initialization routine Can be used to reinitialize device to safe state and -// * send it to sleep -// * @return true if initialisation is successfully -// */ -// bool furi_hal_subghz_init_check(void); - -// /** Switching between internal and external radio -// * @param state SubGhzRadioInternal or SubGhzRadioExternal -// * @return true if switching is successful -// */ -// bool furi_hal_subghz_init_radio_type(SubGhzRadioType state); - -// /** Get current radio -// * @return SubGhzRadioInternal or SubGhzRadioExternal -// */ -// SubGhzRadioType furi_hal_subghz_get_radio_type(void); - -// /** Check for a radio module -// * @return true if check is successful -// */ -// bool furi_hal_subghz_check_radio(void); - -// /** Turn on the power of the external radio module -// * @return true if power-up is successful -// */ -// bool furi_hal_subghz_enable_ext_power(void); - -// /** Turn off the power of the external radio module -// */ -// void furi_hal_subghz_disable_ext_power(void); - -// /** If true - disable 5v power of the external radio module -// */ -// void furi_hal_subghz_set_external_power_disable(bool state); - -// /** Get the current state of the external power disable flag -// */ -// bool furi_hal_subghz_get_external_power_disable(void); - -// /** Set what radio module we will be using -// */ -// void furi_hal_subghz_select_radio_type(SubGhzRadioType state); - -// External CC1101 Ebytes power amplifier control -void furi_hal_subghz_set_ext_power_amp(bool enabled); - -bool furi_hal_subghz_get_ext_power_amp(void); +// External CC1101 Ebytes power amplifier control is now enabled by default #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index 8c650bf0cf..04479a7b85 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -8,6 +8,8 @@ #include #include +#include + #define TAG "FuriHalVersion" #define FURI_HAL_VERSION_OTP_HEADER_MAGIC 0xBABE @@ -241,7 +243,10 @@ uint8_t furi_hal_version_get_hw_body(void) { } FuriHalVersionColor furi_hal_version_get_hw_color(void) { - return furi_hal_version.board_color; + if(momentum_settings.spoof_color == FuriHalVersionColorUnknown) { + return furi_hal_version.board_color; + } + return momentum_settings.spoof_color; } uint8_t furi_hal_version_get_hw_connect(void) { diff --git a/targets/f7/target.json b/targets/f7/target.json index fc1a575839..4c79b2e666 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,6 +22,7 @@ "print", "flipper7", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index 193398a522..458ee501f6 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {}; #include #include +#include #include #include #include diff --git a/targets/furi_hal_include/furi_hal_adc.h b/targets/furi_hal_include/furi_hal_adc.h new file mode 100644 index 0000000000..ecbdad2ca3 --- /dev/null +++ b/targets/furi_hal_include/furi_hal_adc.h @@ -0,0 +1,229 @@ +/** + * @file furi_hal_adc.h + * @brief ADC HAL API + * + * For the sake of simplicity this API implements only small subset + * of what ADC is actually capable of. Feel free to visit Reference + * Manual for STM32WB series and implement any other modes by your + * self. + * + * Couple things to keep in mind: + * + * - ADC resolution is 12 bits, but effective number of bits is ~10 at the best + * and further depends on how you use and configure it. + * - Analog domain is fed from SMPS which is quite noisy. + * - Because of that we use internal on-chip voltage reference for ADC. + * - It's capable of producing 2 voltages: 2.5V and 2.048V. This is the scale + * for your signal. + * - Only single ended mode is available. But you can implement differential one + * by using low level controls directly. + * - No DMA or interrupt API available at this point. But can be implemented + * with low level controls. + * + * + * How to use: + * + * - furi_hal_gpio_init - Configure your pins in `GpioModeAnalog` + * - furi_hal_adc_acquire - acquire ADC handle to work with + * - furi_hal_adc_configure - configure ADC block + * - furi_hal_adc_read - read value + * - furi_hal_adc_release - release ADC handle + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriHalAdcHandle FuriHalAdcHandle; + +typedef enum { + FuriHalAdcScale2048, /**< 2.048V scale */ + FuriHalAdcScale2500, /**< 2.5V scale */ +} FuriHalAdcScale; + +typedef enum { + FuriHalAdcClockSync16, /**< 16MHZ, synchronous */ + FuriHalAdcClockSync32, /**< 32MHZ, synchronous */ + FuriHalAdcClockSync64, /**< 64MHz, synchronous */ +} FuriHalAdcClock; + +typedef enum { + FuriHalAdcOversample2, /**< ADC will take 2 samples per each value */ + FuriHalAdcOversample4, /**< ADC will take 4 samples per each value */ + FuriHalAdcOversample8, /**< ADC will take 8 samples per each value */ + FuriHalAdcOversample16, /**< ADC will take 16 samples per each value */ + FuriHalAdcOversample32, /**< ADC will take 32 samples per each value */ + FuriHalAdcOversample64, /**< ADC will take 64 samples per each value */ + FuriHalAdcOversample128, /**< ADC will take 128 samples per each value */ + FuriHalAdcOversample256, /**< ADC will take 256 samples per each value */ + FuriHalAdcOversampleNone, /**< disable oversampling */ +} FuriHalAdcOversample; + +typedef enum { + FuriHalAdcSamplingtime2_5, /**< Sampling time 2.5 ADC clock */ + FuriHalAdcSamplingtime6_5, /**< Sampling time 6.5 ADC clock */ + FuriHalAdcSamplingtime12_5, /**< Sampling time 12.5 ADC clock */ + FuriHalAdcSamplingtime24_5, /**< Sampling time 24.5 ADC clock */ + FuriHalAdcSamplingtime47_5, /**< Sampling time 47.5 ADC clock */ + FuriHalAdcSamplingtime92_5, /**< Sampling time 92.5 ADC clock */ + FuriHalAdcSamplingtime247_5, /**< Sampling time 247.5 ADC clock */ + FuriHalAdcSamplingtime640_5, /**< Sampling time 640.5 ADC clock */ +} FuriHalAdcSamplingTime; + +typedef enum { + /* Channels 0 - 5 are fast channels */ + FuriHalAdcChannel0, /**< Internal channel, see `FuriHalAdcChannelVREFINT`. */ + FuriHalAdcChannel1, /**< Channel 1p */ + FuriHalAdcChannel2, /**< Channel 2p or 1n */ + FuriHalAdcChannel3, /**< Channel 3p or 2n */ + FuriHalAdcChannel4, /**< Channel 4p or 3n */ + FuriHalAdcChannel5, /**< Channel 5p or 4n */ + /* Channels 6 - 18 are slow channels */ + FuriHalAdcChannel6, /**< Channel 6p or 5n */ + FuriHalAdcChannel7, /**< Channel 7p or 6n */ + FuriHalAdcChannel8, /**< Channel 8p or 7n */ + FuriHalAdcChannel9, /**< Channel 9p or 8n */ + FuriHalAdcChannel10, /**< Channel 10p or 9n */ + FuriHalAdcChannel11, /**< Channel 11p or 10n */ + FuriHalAdcChannel12, /**< Channel 12p or 11n */ + FuriHalAdcChannel13, /**< Channel 13p or 12n */ + FuriHalAdcChannel14, /**< Channel 14p or 13n */ + FuriHalAdcChannel15, /**< Channel 15p or 14n */ + FuriHalAdcChannel16, /**< Channel 16p or 15n */ + FuriHalAdcChannel17, /**< Internal channel, see `FuriHalAdcChannelTEMPSENSOR`. */ + FuriHalAdcChannel18, /**< Internal channel, see `FuriHalAdcChannelVBAT`. */ + /* Special Channels: combines one of the 0-18 channel and additional internal peripherals */ + FuriHalAdcChannelVREFINT, /**< Special channel for VREFINT, used for calibration and self test */ + FuriHalAdcChannelTEMPSENSOR, /**< Special channel for on-die temperature sensor, requires at least 5us of sampling time */ + FuriHalAdcChannelVBAT, /**< Special channel for VBAT/3 voltage, requires at least 12us of sampling time */ + /* Special value to indicate that pin is not connected to ADC */ + FuriHalAdcChannelNone, /**< No channel */ +} FuriHalAdcChannel; + +/** Initialize ADC subsystem */ +void furi_hal_adc_init(void); + +/** Acquire ADC handle + * + * Enables appropriate power and clocking domains + * + * @return FuriHalAdcHandle pointer + */ +FuriHalAdcHandle* furi_hal_adc_acquire(void); + +/** Release ADC handle + * + * @param handle The ADC handle + */ +void furi_hal_adc_release(FuriHalAdcHandle* handle); + +/** Configure with default parameters and enable ADC + * + * Parameters used: + * - FuriHalAdcScale2048 - 2.048V VREF Scale. Your signal should be in 0 - + * 2.048V range. + * - FuriHalAdcClockSync64 - Clocked from sysclk bus at 64MHz in synchronous + * mode. Fast, no delay on data bus access. + * - FuriHalAdcOversample64 - Going to acquire and average 64 samples. For + * circuits with slowly or not changing signal. Total time per one read: + * (1/64)*(12.5+247.5)*64 = 260us. The best results you'll get if your signal + * will stay on the same level all this time. + * - FuriHalAdcSamplingtime247_5 - Sampling(transfer from source to internal + * sampling capacitor) time is 247.5 ADC clocks: (1/64)*247.5 = 3.8671875us. + * For relatively high impedance circuits. + * + * Also keep your measurement circuit impedance under 10KOhm or oversampling + * results will be compromised. Verify your signal with oscilloscope(you may + * need fast oscilloscope: 200MHz bandwidth, 125MS/s), ensure that signal is not + * distorted by sampling. + * + * Those parameters were optimized for 0 - 2.048 voltage measurement with ~0.1% + * precision. You can get more, but it will require some magic. + * + * @param handle The ADC handle + */ +void furi_hal_adc_configure(FuriHalAdcHandle* handle); + +/** Configure with extended parameters and enable ADC + * + * General advice is to start with default parameters, figure out what exactly + * is not working for you and then tune it. Also in some cases changing + * circuit(adding caps, lowering impedance, adding opamp) may be better than changing + * parameters. + * + * @warning In general ADC is a world of magic: make sure that you understand + * how your circuit and ADC works. Then carefully read STM32WB + * series reference manual. Setting incorrect parameters leads to + * very poor results. Also internal channels require special + * settings. + * + * @param handle The ADC handle + * @param[in] scale The ADC voltage scale + * @param[in] clock The ADC clock + * @param[in] oversample The ADC oversample mode + * @param[in] sampling_time The ADC sampling time + */ +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time); + +/** Read single ADC value + * + * @param handle The ADC handle + * @param[in] channel The channel to sample + * + * @return value, 12 bits + */ +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel); + +/** Convert sampled value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VREFINT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVREFINT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled TEMPSENSOR value to temperature + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelTEMPSENSOR` channel + * + * @return temperature in degree C + */ +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VBAT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVBAT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value); + +#ifdef __cplusplus +} +#endif diff --git a/targets/furi_hal_include/furi_hal_random.h b/targets/furi_hal_include/furi_hal_random.h index 20c6c3357d..fab62083f1 100644 --- a/targets/furi_hal_include/furi_hal_random.h +++ b/targets/furi_hal_include/furi_hal_random.h @@ -6,7 +6,7 @@ extern "C" { #endif -#define FURI_HAL_RANDOM_MAX 0xFFFFFFFF +#define FURI_HAL_RANDOM_MAX 0xFFFFFFFFU /** Initialize random subsystem */ void furi_hal_random_init(void); diff --git a/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h index 0cdea50733..bbcb77c0d5 100644 --- a/targets/furi_hal_include/furi_hal_version.h +++ b/targets/furi_hal_include/furi_hal_version.h @@ -35,6 +35,7 @@ typedef enum { FuriHalVersionColorBlack = 0x01, FuriHalVersionColorWhite = 0x02, FuriHalVersionColorTransparent = 0x03, + FuriHalVersionColorCount, } FuriHalVersionColor; /** Device Regions */