From fb2bff6c4dce34b3715ff789a5b98ed8b49bfd65 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sat, 3 Feb 2024 01:33:36 +0900 Subject: [PATCH 01/36] Update ReadMe.md recomeded -> recommended --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index ee3d394bf..f0e45d585 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -194,7 +194,7 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM ### - [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recomeded, please use embedded configurator +### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recommended, please use embedded configurator ## **Plugins** From 35674a306b1770a53c89b26b870b6e343a473bdd Mon Sep 17 00:00:00 2001 From: Methodius Date: Mon, 5 Feb 2024 20:53:08 +0900 Subject: [PATCH 02/36] Kazan parser improved: token parse option added --- .../main/nfc/plugins/supported_cards/kazan.c | 110 +++++++++++++----- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index a1bcbb1f8..05acc4a99 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -34,7 +34,7 @@ typedef struct { uint64_t b; } MfClassicKeyPair; -static const MfClassicKeyPair kazan_1k_keys_standart[] = { +static const MfClassicKeyPair kazan_1k_keys_v1[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, @@ -53,7 +53,7 @@ static const MfClassicKeyPair kazan_1k_keys_standart[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, }; -static const MfClassicKeyPair kazan_1k_keys_old[] = { +static const MfClassicKeyPair kazan_1k_keys_v2[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, @@ -72,6 +72,25 @@ static const MfClassicKeyPair kazan_1k_keys_old[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, }; +static const MfClassicKeyPair kazan_1k_keys_v3[] = { + {.a = 0x165D3B5280C0, .b = 0xFC7C2BB34E0F}, + {.a = 0xC178E3DA7A39, .b = 0xC70FB78B4934}, + {.a = 0x1BDF96089D2F, .b = 0x9500F058ABC5}, + {.a = 0xB65AA70AD524, .b = 0x733A63B8B7F3}, + {.a = 0x8BDB8FECDCAF, .b = 0xB0048EE71C0F}, + {.a = 0xBC10468ABF05, .b = 0x1700A7D5C034}, + {.a = 0xE7F3282E0C7D, .b = 0x65909B89BDA5}, + {.a = 0x986C63DD0355, .b = 0x901C125ED37D}, + {.a = 0x2058EAEE8446, .b = 0xCB9B23815F87}, + {.a = 0x492F3744A1DC, .b = 0x6B770AADA274}, + {.a = 0x87EB933B9BF7, .b = 0xFC98A9460EE5}, + {.a = 0x7EBC8337F8F0, .b = 0x887C97E53DBC}, + {.a = 0xA369BF6D4452, .b = 0x03BBA7CA2F24}, + {.a = 0x37569D7992EF, .b = 0x710BBD01B3B8}, + {.a = 0xD4AA94C4B5E8, .b = 0x7F5C4D210F0B}, + {.a = 0x521B8C4B2123, .b = 0x2D2392CC43A7}, +}; + enum SubscriptionType { SUBSCRIPTION_TYPE_UNKNOWN, SUBSCRIPTION_TYPE_PURSE, @@ -96,6 +115,9 @@ enum SubscriptionType get_subscription_type(uint8_t value, FuriString* tariff_na case 0x53: furi_string_printf(tariff_name, "Standart purse"); return SUBSCRIPTION_TYPE_PURSE; + case 0x01: + furi_string_printf(tariff_name, "Token"); + return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS; default: furi_string_printf(tariff_name, "Unknown"); return SUBSCRIPTION_TYPE_UNKNOWN; @@ -106,21 +128,34 @@ static bool kazan_verify(Nfc* nfc) { bool verified = false; do { - const uint8_t verification_sector_number = 10; + const uint8_t verification_sector_number = 8; const uint8_t verification_block_number = mf_classic_get_first_block_num_of_sector(verification_sector_number) + 1; FURI_LOG_D(TAG, "Verifying sector %u", verification_sector_number); - MfClassicKey key = {0}; + MfClassicKey key_1 = {0}; nfc_util_num2bytes( - kazan_1k_keys_standart[verification_sector_number].a, COUNT_OF(key.data), key.data); + kazan_1k_keys_v1[verification_sector_number].a, COUNT_OF(key_1.data), key_1.data); MfClassicAuthContext auth_context; MfClassicError error = mf_classic_poller_sync_auth( - nfc, verification_block_number, &key, MfClassicKeyTypeA, &auth_context); + nfc, verification_block_number, &key_1, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", verification_block_number, error); - break; + FURI_LOG_D( + TAG, "Failed to read block %u: %d. Keys: v1", verification_block_number, error); + + MfClassicKey key_2 = {0}; + nfc_util_num2bytes( + kazan_1k_keys_v2[verification_sector_number].a, COUNT_OF(key_2.data), key_2.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, verification_block_number, &key_2, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d. Keys: v2", verification_block_number, error); + break; + } } verified = true; @@ -145,41 +180,57 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { data->type = type; if(type != MfClassicType1k) break; - MfClassicDeviceKeys keys = { + MfClassicDeviceKeys keys_v1 = { .key_a_mask = 0, .key_b_mask = 0, }; - MfClassicDeviceKeys keys_old = { + MfClassicDeviceKeys keys_v2 = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + + MfClassicDeviceKeys keys_v3 = { .key_a_mask = 0, .key_b_mask = 0, }; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes( - kazan_1k_keys_standart[i].a, sizeof(MfClassicKey), keys.key_a[i].data); - nfc_util_num2bytes( - kazan_1k_keys_old[i].a, sizeof(MfClassicKey), keys_old.key_a[i].data); - FURI_BIT_SET(keys.key_a_mask, i); - FURI_BIT_SET(keys_old.key_a_mask, i); + nfc_util_num2bytes(kazan_1k_keys_v1[i].a, sizeof(MfClassicKey), keys_v1.key_a[i].data); + nfc_util_num2bytes(kazan_1k_keys_v2[i].a, sizeof(MfClassicKey), keys_v2.key_a[i].data); + nfc_util_num2bytes(kazan_1k_keys_v3[i].a, sizeof(MfClassicKey), keys_v3.key_a[i].data); - nfc_util_num2bytes( - kazan_1k_keys_standart[i].b, sizeof(MfClassicKey), keys.key_b[i].data); - nfc_util_num2bytes( - kazan_1k_keys_old[i].b, sizeof(MfClassicKey), keys_old.key_b[i].data); - FURI_BIT_SET(keys.key_b_mask, i); - FURI_BIT_SET(keys_old.key_b_mask, i); + FURI_BIT_SET(keys_v1.key_a_mask, i); + FURI_BIT_SET(keys_v2.key_a_mask, i); + FURI_BIT_SET(keys_v3.key_a_mask, i); + + nfc_util_num2bytes(kazan_1k_keys_v1[i].b, sizeof(MfClassicKey), keys_v1.key_b[i].data); + nfc_util_num2bytes(kazan_1k_keys_v2[i].b, sizeof(MfClassicKey), keys_v2.key_b[i].data); + nfc_util_num2bytes(kazan_1k_keys_v3[i].b, sizeof(MfClassicKey), keys_v3.key_b[i].data); + + FURI_BIT_SET(keys_v1.key_b_mask, i); + FURI_BIT_SET(keys_v2.key_b_mask, i); + FURI_BIT_SET(keys_v3.key_b_mask, i); } - error = mf_classic_poller_sync_read(nfc, &keys, data); + error = mf_classic_poller_sync_read(nfc, &keys_v1, data); if(error != MfClassicErrorNone) { - FURI_LOG_W(TAG, "Failed to read data: standart keys"); + FURI_LOG_W(TAG, "Failed to read data: keys_v1"); break; } + if(!mf_classic_is_card_read(data)) { - error = mf_classic_poller_sync_read(nfc, &keys_old, data); + error = mf_classic_poller_sync_read(nfc, &keys_v2, data); if(error != MfClassicErrorNone) { - FURI_LOG_W(TAG, "Failed to read data: old keys"); + FURI_LOG_W(TAG, "Failed to read data: keys_v1"); + break; + } + } + + if(!mf_classic_is_card_read(data)) { + error = mf_classic_poller_sync_read(nfc, &keys_v3, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data: keys_v3"); break; } } @@ -202,18 +253,19 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { bool parsed = false; do { - const uint8_t verification_sector_number = 10; const uint8_t ticket_sector_number = 8; const uint8_t balance_sector_number = 9; // Verify keys MfClassicKeyPair keys = {}; const MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, verification_sector_number); + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); - if(keys.a != 0xF7A545095C49) { + if(((keys.a != kazan_1k_keys_v1[8].a) && (keys.a != kazan_1k_keys_v2[8].a)) || + ((keys.b != kazan_1k_keys_v1[8].b) && (keys.b != kazan_1k_keys_v2[8].b))) { FURI_LOG_D(TAG, "Parser: Failed to verify key a: %llu", keys.a); break; } From 2f0adf73c7482af9b22c3e7877a42667dd055b1f Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 15:41:59 +0300 Subject: [PATCH 03/36] Separate expansion control and worker threads --- applications/services/expansion/expansion.c | 575 ++++++------------ .../services/expansion/expansion_worker.c | 399 ++++++++++++ .../services/expansion/expansion_worker.h | 20 + 3 files changed, 619 insertions(+), 375 deletions(-) create mode 100644 applications/services/expansion/expansion_worker.c create mode 100644 applications/services/expansion/expansion_worker.h diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index e385734b7..14eaae28c 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -1,19 +1,17 @@ #include "expansion.h" -#include -#include #include #include +#include -#include - +#include "expansion_worker.h" #include "expansion_settings.h" -#include "expansion_protocol.h" #define TAG "ExpansionSrv" -#define EXPANSION_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) +#define EXPANSION_CONTROL_QUEUE_SIZE (8UL) +#define EXPANSION_CONTROL_STACK_SIZE (1024UL) typedef enum { ExpansionStateDisabled, @@ -22,368 +20,197 @@ typedef enum { } ExpansionState; typedef enum { - ExpansionSessionStateHandShake, - ExpansionSessionStateConnected, - ExpansionSessionStateRpcActive, -} ExpansionSessionState; + ExpansionMessageTypeEnable, + ExpansionMessageTypeDisable, + ExpansionMessageTypeSetListenSerial, + ExpansionMessageTypeModuleConnected, + ExpansionMessageTypeModuleDisconnected, +} ExpansionMessageType; -typedef enum { - ExpansionSessionExitReasonUnknown, - ExpansionSessionExitReasonUser, - ExpansionSessionExitReasonError, - ExpansionSessionExitReasonTimeout, -} ExpansionSessionExitReason; +typedef union { + FuriHalSerialId serial_id; +} ExpansionMessageData; -typedef enum { - ExpansionFlagStop = 1 << 0, - ExpansionFlagData = 1 << 1, - ExpansionFlagError = 1 << 2, -} ExpansionFlag; - -#define EXPANSION_ALL_FLAGS (ExpansionFlagData | ExpansionFlagStop) +typedef struct { + ExpansionMessageType type; + ExpansionMessageData data; + FuriApiLock api_lock; +} ExpansionMessage; struct Expansion { - ExpansionState state; - ExpansionSessionState session_state; - ExpansionSessionExitReason exit_reason; - FuriStreamBuffer* rx_buf; - FuriSemaphore* tx_semaphore; - FuriMutex* state_mutex; - FuriThread* worker_thread; + FuriThread* thread; + FuriMessageQueue* queue; FuriHalSerialId serial_id; - FuriHalSerialHandle* serial_handle; - RpcSession* rpc_session; + ExpansionWorker* worker; + ExpansionState state; }; -static void expansion_detect_callback(void* context); - -// Called in UART IRQ context -static void expansion_serial_rx_callback( - FuriHalSerialHandle* handle, - FuriHalSerialRxEvent event, - void* context) { - furi_assert(handle); - furi_assert(context); - - Expansion* instance = context; - - if(event == FuriHalSerialRxEventData) { - const uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagData); - } -} - -static size_t expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { - Expansion* instance = context; - - size_t received_size = 0; - - while(true) { - received_size += furi_stream_buffer_receive( - instance->rx_buf, data + received_size, data_size - received_size, 0); - - if(received_size == data_size) break; - - const uint32_t flags = furi_thread_flags_wait( - EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); - - if(flags & FuriFlagError) { - if(flags == (unsigned)FuriFlagErrorTimeout) { - // Exiting due to timeout - instance->exit_reason = ExpansionSessionExitReasonTimeout; - } else { - // Exiting due to an unspecified error - instance->exit_reason = ExpansionSessionExitReasonError; - } - break; - } else if(flags & ExpansionFlagStop) { - // Exiting due to explicit request - instance->exit_reason = ExpansionSessionExitReasonUser; - break; - } else if(flags & ExpansionFlagError) { - // Exiting due to RPC error - instance->exit_reason = ExpansionSessionExitReasonError; - break; - } else if(flags & ExpansionFlagData) { - // Go to buffer reading - continue; - } - } - - return received_size; -} - -static inline bool expansion_receive_frame(Expansion* instance, ExpansionFrame* frame) { - return expansion_protocol_decode(frame, expansion_receive_callback, instance) == - ExpansionProtocolStatusOk; -} - -static size_t expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { - Expansion* instance = context; - furi_hal_serial_tx(instance->serial_handle, data, data_size); - furi_hal_serial_tx_wait_complete(instance->serial_handle); - return data_size; -} - -static inline bool expansion_send_frame(Expansion* instance, const ExpansionFrame* frame) { - return expansion_protocol_encode(frame, expansion_send_callback, instance) == - ExpansionProtocolStatusOk; -} - -static bool expansion_send_heartbeat(Expansion* instance) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeHeartbeat, - .content.heartbeat = {}, - }; - - return expansion_send_frame(instance, &frame); -} - -static bool expansion_send_status_response(Expansion* instance, ExpansionFrameError error) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeStatus, - .content.status.error = error, - }; - - return expansion_send_frame(instance, &frame); -} - -static bool - expansion_send_data_response(Expansion* instance, const uint8_t* data, size_t data_size) { - furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); - - ExpansionFrame frame = { - .header.type = ExpansionFrameTypeData, - .content.data.size = data_size, - }; - - memcpy(frame.content.data.bytes, data, data_size); - return expansion_send_frame(instance, &frame); -} - -// Called in Rpc session thread context -static void expansion_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { - Expansion* instance = context; - - for(size_t sent_data_size = 0; sent_data_size < data_size;) { - if(furi_semaphore_acquire( - instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != - FuriStatusOk) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagError); - break; - } - - const size_t current_data_size = - MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); - if(!expansion_send_data_response(instance, data + sent_data_size, current_data_size)) - break; - sent_data_size += current_data_size; - } -} - -static bool expansion_rpc_session_open(Expansion* instance) { - Rpc* rpc = furi_record_open(RECORD_RPC); - instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); - - if(instance->rpc_session) { - instance->tx_semaphore = furi_semaphore_alloc(1, 1); - rpc_session_set_context(instance->rpc_session, instance); - rpc_session_set_send_bytes_callback(instance->rpc_session, expansion_rpc_send_callback); - } - - return instance->rpc_session != NULL; -} - -static void expansion_rpc_session_close(Expansion* instance) { - if(instance->rpc_session) { - rpc_session_close(instance->rpc_session); - furi_semaphore_free(instance->tx_semaphore); - } - - furi_record_close(RECORD_RPC); -} - -static bool - expansion_handle_session_state_handshake(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; - const uint32_t baud_rate = rx_frame->content.baud_rate.baud; - - FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); - - if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { - instance->session_state = ExpansionSessionStateConnected; - // Send response at previous baud rate - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - furi_hal_serial_set_br(instance->serial_handle, baud_rate); - - } else { - if(!expansion_send_status_response(instance, ExpansionFrameErrorBaudRate)) break; - FURI_LOG_E(TAG, "Bad baud rate"); - } - success = true; - } while(false); - - return success; -} - -static bool - expansion_handle_session_state_connected(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; - instance->session_state = ExpansionSessionStateRpcActive; - if(!expansion_rpc_session_open(instance)) break; - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; - - } else { - break; - } - success = true; - } while(false); - - return success; -} - -static bool - expansion_handle_session_state_rpc_active(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type == ExpansionFrameTypeData) { - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - const size_t size_consumed = rpc_session_feed( - instance->rpc_session, - rx_frame->content.data.bytes, - rx_frame->content.data.size, - EXPANSION_PROTOCOL_TIMEOUT_MS); - if(size_consumed != rx_frame->content.data.size) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; - instance->session_state = ExpansionSessionStateConnected; - expansion_rpc_session_close(instance); - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { - if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; - furi_semaphore_release(instance->tx_semaphore); - - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; - - } else { - break; - } - success = true; - } while(false); - - return success; -} - -static inline void expansion_state_machine(Expansion* instance) { - typedef bool (*ExpansionSessionStateHandler)(Expansion*, const ExpansionFrame*); - - static const ExpansionSessionStateHandler expansion_handlers[] = { - [ExpansionSessionStateHandShake] = expansion_handle_session_state_handshake, - [ExpansionSessionStateConnected] = expansion_handle_session_state_connected, - [ExpansionSessionStateRpcActive] = expansion_handle_session_state_rpc_active, - }; - - ExpansionFrame rx_frame; - - while(true) { - if(!expansion_receive_frame(instance, &rx_frame)) break; - if(!expansion_handlers[instance->session_state](instance, &rx_frame)) break; - } -} - -static void expansion_worker_pending_callback(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - Expansion* instance = context; - furi_thread_join(instance->worker_thread); - - // Do not re-enable detection interrupt on user-requested exit - if(instance->exit_reason != ExpansionSessionExitReasonUser) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - instance->state = ExpansionStateEnabled; - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - furi_mutex_release(instance->state_mutex); - } -} - -static int32_t expansion_worker(void* context) { - furi_assert(context); - Expansion* instance = context; - - furi_hal_power_insomnia_enter(); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - - instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); - furi_check(instance->serial_handle); - - FURI_LOG_D(TAG, "Service started"); - - instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_BUFFER_SIZE, 1); - instance->session_state = ExpansionSessionStateHandShake; - instance->exit_reason = ExpansionSessionExitReasonUnknown; - - furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); - - furi_hal_serial_async_rx_start( - instance->serial_handle, expansion_serial_rx_callback, instance, false); - - if(expansion_send_heartbeat(instance)) { - expansion_state_machine(instance); - } - - if(instance->session_state == ExpansionSessionStateRpcActive) { - expansion_rpc_session_close(instance); - } - - FURI_LOG_D(TAG, "Service stopped"); - - furi_hal_serial_control_release(instance->serial_handle); - furi_stream_buffer_free(instance->rx_buf); - - furi_hal_power_insomnia_exit(); - furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); - - return 0; -} +static const char* const expansion_uart_names[] = { + "USART", + "LPUART", +}; // Called from the serial control thread static void expansion_detect_callback(void* context) { furi_assert(context); Expansion* instance = context; - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleConnected, + .api_lock = NULL, // Not locking the API here + }; - if(instance->state == ExpansionStateEnabled) { - instance->state = ExpansionStateRunning; - furi_thread_start(instance->worker_thread); + // Not waiting for available queue space, discarding message if there is none + const FuriStatus status = furi_message_queue_put(instance->queue, &message, 0); + UNUSED(status); +} + +static void expansion_worker_callback(void* context) { + furi_assert(context); + Expansion* instance = context; + + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleDisconnected, + .api_lock = NULL, // Not locking the API here + }; + + const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); +} + +static void + expansion_control_handler_enable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); + + if(instance->state != ExpansionStateDisabled) { + return; } - furi_mutex_release(instance->state_mutex); + ExpansionSettings settings = {0}; + if(!expansion_settings_load(&settings)) { + expansion_settings_save(&settings); + } + + if(settings.uart_index < FuriHalSerialIdMax) { + instance->state = ExpansionStateEnabled; + instance->serial_id = settings.uart_index; + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + + FURI_LOG_D(TAG, "Detection enabled on %s", expansion_uart_names[instance->serial_id]); + } +} + +static void + expansion_control_handler_disable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); + + if(instance->state == ExpansionStateDisabled) { + return; + } + + if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); + } + + instance->state = ExpansionStateDisabled; + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + + FURI_LOG_D(TAG, "Detection disabled"); +} + +static void expansion_control_handler_set_listen_serial( + Expansion* instance, + const ExpansionMessageData* data) { + furi_check(data->serial_id < FuriHalSerialIdMax); + + if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); + + } else if(instance->state == ExpansionStateEnabled) { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } + + instance->state = ExpansionStateEnabled; + instance->serial_id = data->serial_id; + + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + + FURI_LOG_D(TAG, "Listen serial changed to %s", expansion_uart_names[instance->serial_id]); +} + +static void expansion_control_handler_module_connected( + Expansion* instance, + const ExpansionMessageData* data) { + furi_check(instance->state != ExpansionStateDisabled); + UNUSED(data); + + if(instance->state == ExpansionStateRunning) { + return; + } + + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + + instance->state = ExpansionStateRunning; + instance->worker = expansion_worker_alloc(instance->serial_id); + + expansion_worker_set_callback(instance->worker, expansion_worker_callback, instance); + expansion_worker_start(instance->worker); +} + +static void expansion_control_handler_module_disconnected( + Expansion* instance, + const ExpansionMessageData* data) { + furi_check(instance->state == ExpansionStateRunning); + UNUSED(data); + + instance->state = ExpansionStateEnabled; + expansion_worker_free(instance->worker); + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); +} + +typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); + +static const ExpansionControlHandler expansion_control_handlers[] = { + [ExpansionMessageTypeEnable] = expansion_control_handler_enable, + [ExpansionMessageTypeDisable] = expansion_control_handler_disable, + [ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial, + [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, + [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, +}; + +static int32_t expansion_control(void* context) { + furi_assert(context); + Expansion* instance = context; + + for(;;) { + ExpansionMessage message; + + FuriStatus status = furi_message_queue_get(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); + + furi_check(message.type < COUNT_OF(expansion_control_handlers)); + expansion_control_handlers[message.type](instance, &message.data); + + if(message.api_lock != NULL) { + api_lock_unlock(message.api_lock); + } + } + + return 0; } static Expansion* expansion_alloc() { Expansion* instance = malloc(sizeof(Expansion)); - instance->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - instance->worker_thread = furi_thread_alloc_ex(TAG, 768, expansion_worker, instance); + instance->queue = + furi_message_queue_alloc(EXPANSION_CONTROL_QUEUE_SIZE, sizeof(ExpansionMessage)); + instance->thread = + furi_thread_alloc_ex(TAG, EXPANSION_CONTROL_STACK_SIZE, expansion_control, instance); return instance; } @@ -394,48 +221,46 @@ void expansion_on_system_start(void* arg) { Expansion* instance = expansion_alloc(); furi_record_create(RECORD_EXPANSION, instance); + furi_thread_start(instance->thread); expansion_enable(instance); } // Public API functions void expansion_enable(Expansion* instance) { - ExpansionSettings settings = {}; - if(!expansion_settings_load(&settings)) { - expansion_settings_save(&settings); - } else if(settings.uart_index < FuriHalSerialIdMax) { - expansion_set_listen_serial(instance, settings.uart_index); - } + furi_check(instance); + + ExpansionMessage message = { + .type = ExpansionMessageTypeEnable, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_disable(Expansion* instance) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + furi_check(instance); - if(instance->state == ExpansionStateRunning) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagStop); - furi_thread_join(instance->worker_thread); - } else if(instance->state == ExpansionStateEnabled) { - FURI_LOG_D(TAG, "Detection disabled"); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - } + ExpansionMessage message = { + .type = ExpansionMessageTypeDisable, + .api_lock = api_lock_alloc_locked(), + }; - instance->state = ExpansionStateDisabled; - - furi_mutex_release(instance->state_mutex); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { - expansion_disable(instance); + furi_check(instance); + furi_check(serial_id < FuriHalSerialIdMax); - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + ExpansionMessage message = { + .type = ExpansionMessageTypeSetListenSerial, + .data.serial_id = serial_id, + .api_lock = api_lock_alloc_locked(), + }; - instance->serial_id = serial_id; - instance->state = ExpansionStateEnabled; - - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - - furi_mutex_release(instance->state_mutex); - - FURI_LOG_D(TAG, "Detection enabled"); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c new file mode 100644 index 000000000..4ffabf738 --- /dev/null +++ b/applications/services/expansion/expansion_worker.c @@ -0,0 +1,399 @@ +#include "expansion_worker.h" + +#include +#include +#include + +#include +#include + +#include "expansion_protocol.h" + +#define TAG "ExpansionSrv" + +#define EXPANSION_WORKER_STACK_SZIE (768UL) +#define EXPANSION_WORKER_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) + +typedef enum { + ExpansionWorkerStateHandShake, + ExpansionWorkerStateConnected, + ExpansionWorkerStateRpcActive, +} ExpansionWorkerState; + +typedef enum { + ExpansionWorkerExitReasonUnknown, + ExpansionWorkerExitReasonUser, + ExpansionWorkerExitReasonError, + ExpansionWorkerExitReasonTimeout, +} ExpansionWorkerExitReason; + +typedef enum { + ExpansionWorkerFlagStop = 1 << 0, + ExpansionWorkerFlagData = 1 << 1, + ExpansionWorkerFlagError = 1 << 2, +} ExpansionWorkerFlag; + +#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) + +struct ExpansionWorker { + FuriThread* thread; + FuriStreamBuffer* rx_buf; + FuriSemaphore* tx_semaphore; + + FuriHalSerialId serial_id; + FuriHalSerialHandle* serial_handle; + + RpcSession* rpc_session; + + ExpansionWorkerState state; + ExpansionWorkerExitReason exit_reason; + ExpansionWorkerCallback callback; + void* cb_context; +}; + +// Called in UART IRQ context +static void expansion_worker_serial_rx_callback( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { + furi_assert(handle); + furi_assert(context); + + ExpansionWorker* instance = context; + + if(event == FuriHalSerialRxEventData) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagData); + } +} + +static size_t expansion_worker_receive_callback(uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + + size_t received_size = 0; + + while(true) { + received_size += furi_stream_buffer_receive( + instance->rx_buf, data + received_size, data_size - received_size, 0); + + if(received_size == data_size) break; + + const uint32_t flags = furi_thread_flags_wait( + EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); + + if(flags & FuriFlagError) { + if(flags == (unsigned)FuriFlagErrorTimeout) { + // Exiting due to timeout + instance->exit_reason = ExpansionWorkerExitReasonTimeout; + } else { + // Exiting due to an unspecified error + instance->exit_reason = ExpansionWorkerExitReasonError; + } + break; + } else if(flags & ExpansionWorkerFlagStop) { + // Exiting due to explicit request + instance->exit_reason = ExpansionWorkerExitReasonUser; + break; + } else if(flags & ExpansionWorkerFlagError) { + // Exiting due to RPC error + instance->exit_reason = ExpansionWorkerExitReasonError; + break; + } else if(flags & ExpansionWorkerFlagData) { + // Go to buffer reading + continue; + } + } + + return received_size; +} + +static inline bool + expansion_worker_receive_frame(ExpansionWorker* instance, ExpansionFrame* frame) { + return expansion_protocol_decode(frame, expansion_worker_receive_callback, instance) == + ExpansionProtocolStatusOk; +} + +static size_t + expansion_worker_send_callback(const uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + furi_hal_serial_tx(instance->serial_handle, data, data_size); + furi_hal_serial_tx_wait_complete(instance->serial_handle); + return data_size; +} + +static inline bool + expansion_worker_send_frame(ExpansionWorker* instance, const ExpansionFrame* frame) { + return expansion_protocol_encode(frame, expansion_worker_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_worker_send_heartbeat(ExpansionWorker* instance) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeHeartbeat, + .content.heartbeat = {}, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool + expansion_worker_send_status_response(ExpansionWorker* instance, ExpansionFrameError error) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeStatus, + .content.status.error = error, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool expansion_worker_send_data_response( + ExpansionWorker* instance, + const uint8_t* data, + size_t data_size) { + furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); + + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeData, + .content.data.size = data_size, + }; + + memcpy(frame.content.data.bytes, data, data_size); + return expansion_worker_send_frame(instance, &frame); +} + +// Called in Rpc session thread context +static void expansion_worker_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { + ExpansionWorker* instance = context; + + for(size_t sent_data_size = 0; sent_data_size < data_size;) { + if(furi_semaphore_acquire( + instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != + FuriStatusOk) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + break; + } + + const size_t current_data_size = + MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); + if(!expansion_worker_send_data_response(instance, data + sent_data_size, current_data_size)) + break; + sent_data_size += current_data_size; + } +} + +static bool expansion_worker_rpc_session_open(ExpansionWorker* instance) { + Rpc* rpc = furi_record_open(RECORD_RPC); + instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); + + if(instance->rpc_session) { + instance->tx_semaphore = furi_semaphore_alloc(1, 1); + rpc_session_set_context(instance->rpc_session, instance); + rpc_session_set_send_bytes_callback( + instance->rpc_session, expansion_worker_rpc_send_callback); + } + + return instance->rpc_session != NULL; +} + +static void expansion_worker_rpc_session_close(ExpansionWorker* instance) { + if(instance->rpc_session) { + rpc_session_close(instance->rpc_session); + furi_semaphore_free(instance->tx_semaphore); + } + + furi_record_close(RECORD_RPC); +} + +static bool expansion_worker_handle_state_handshake( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; + const uint32_t baud_rate = rx_frame->content.baud_rate.baud; + + FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); + + if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { + instance->state = ExpansionWorkerStateConnected; + // Send response at previous baud rate + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + furi_hal_serial_set_br(instance->serial_handle, baud_rate); + + } else { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorBaudRate)) + break; + FURI_LOG_E(TAG, "Bad baud rate"); + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_connected( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; + instance->state = ExpansionWorkerStateRpcActive; + if(!expansion_worker_rpc_session_open(instance)) break; + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_rpc_active( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeData) { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + const size_t size_consumed = rpc_session_feed( + instance->rpc_session, + rx_frame->content.data.bytes, + rx_frame->content.data.size, + EXPANSION_PROTOCOL_TIMEOUT_MS); + if(size_consumed != rx_frame->content.data.size) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { + if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; + furi_semaphore_release(instance->tx_semaphore); + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +typedef bool (*ExpansionWorkerStateHandler)(ExpansionWorker*, const ExpansionFrame*); + +static const ExpansionWorkerStateHandler expansion_handlers[] = { + [ExpansionWorkerStateHandShake] = expansion_worker_handle_state_handshake, + [ExpansionWorkerStateConnected] = expansion_worker_handle_state_connected, + [ExpansionWorkerStateRpcActive] = expansion_worker_handle_state_rpc_active, +}; + +static inline void expansion_worker_state_machine(ExpansionWorker* instance) { + ExpansionFrame rx_frame; + + while(true) { + if(!expansion_worker_receive_frame(instance, &rx_frame)) break; + if(!expansion_handlers[instance->state](instance, &rx_frame)) break; + } +} + +static void expansion_worker_pending_callback(void* context, uint32_t arg) { + furi_assert(context); + UNUSED(arg); + + ExpansionWorker* instance = context; + furi_thread_join(instance->thread); + + if(instance->callback != NULL) { + instance->callback(instance->cb_context); + } +} + +static int32_t expansion_worker(void* context) { + furi_assert(context); + ExpansionWorker* instance = context; + + furi_hal_power_insomnia_enter(); + + instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); + furi_check(instance->serial_handle); + + FURI_LOG_D(TAG, "Worker started"); + + instance->state = ExpansionWorkerStateHandShake; + instance->exit_reason = ExpansionWorkerExitReasonUnknown; + + furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + + furi_hal_serial_async_rx_start( + instance->serial_handle, expansion_worker_serial_rx_callback, instance, false); + + if(expansion_worker_send_heartbeat(instance)) { + expansion_worker_state_machine(instance); + } + + if(instance->state == ExpansionWorkerStateRpcActive) { + expansion_worker_rpc_session_close(instance); + } + + FURI_LOG_D(TAG, "Worker stopped"); + + furi_hal_serial_control_release(instance->serial_handle); + furi_hal_power_insomnia_exit(); + + // Do not invoke pending callback on user-requested exit + if(instance->exit_reason != ExpansionWorkerExitReasonUser) { + furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); + } + + return 0; +} + +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { + ExpansionWorker* instance = malloc(sizeof(ExpansionWorker)); + + instance->thread = + furi_thread_alloc_ex(TAG, EXPANSION_WORKER_STACK_SZIE, expansion_worker, instance); + instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_WORKER_BUFFER_SIZE, 1); + instance->serial_id = serial_id; + + return instance; +} + +void expansion_worker_free(ExpansionWorker* instance) { + furi_stream_buffer_free(instance->rx_buf); + furi_thread_free(instance->thread); + free(instance); +} + +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context) { + instance->callback = callback; + instance->cb_context = context; +} + +void expansion_worker_start(ExpansionWorker* instance) { + furi_thread_start(instance->thread); +} + +void expansion_worker_stop(ExpansionWorker* instance) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagStop); + furi_thread_join(instance->thread); +} diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h new file mode 100644 index 000000000..0d497ff81 --- /dev/null +++ b/applications/services/expansion/expansion_worker.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +typedef struct ExpansionWorker ExpansionWorker; + +typedef void (*ExpansionWorkerCallback)(void*); + +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id); + +void expansion_worker_free(ExpansionWorker* instance); + +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context); + +void expansion_worker_start(ExpansionWorker* instance); + +void expansion_worker_stop(ExpansionWorker* instance); From 6846e808cf85da771633f614c48056c8dfbdd739 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 16:02:46 +0300 Subject: [PATCH 04/36] Add edge case checks --- applications/services/expansion/expansion.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 14eaae28c..db9844c86 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -164,13 +164,16 @@ static void expansion_control_handler_module_connected( static void expansion_control_handler_module_disconnected( Expansion* instance, const ExpansionMessageData* data) { - furi_check(instance->state == ExpansionStateRunning); UNUSED(data); - - instance->state = ExpansionStateEnabled; - expansion_worker_free(instance->worker); - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); + // This condition should be always true, but in some rare edge cases + // it is possible to change the settings while the module was being + // disconnected, hence the additional check. + if(instance->state == ExpansionStateRunning) { + instance->state = ExpansionStateEnabled; + expansion_worker_free(instance->worker); + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + } } typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); @@ -220,8 +223,8 @@ void expansion_on_system_start(void* arg) { Expansion* instance = expansion_alloc(); furi_record_create(RECORD_EXPANSION, instance); - furi_thread_start(instance->thread); + expansion_enable(instance); } From 3721a71eabcf12c86a49102b07f8443c3cecedc4 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 16:38:54 +0300 Subject: [PATCH 05/36] Reduce expansion control thread stack size, add comments --- applications/services/expansion/expansion.c | 2 +- .../services/expansion/expansion_worker.c | 4 +- .../services/expansion/expansion_worker.h | 60 ++++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index db9844c86..0b28f5a49 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -11,7 +11,7 @@ #define TAG "ExpansionSrv" #define EXPANSION_CONTROL_QUEUE_SIZE (8UL) -#define EXPANSION_CONTROL_STACK_SIZE (1024UL) +#define EXPANSION_CONTROL_STACK_SIZE (768UL) typedef enum { ExpansionStateDisabled, diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index 4ffabf738..eef51d2f6 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -367,8 +367,8 @@ static int32_t expansion_worker(void* context) { ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { ExpansionWorker* instance = malloc(sizeof(ExpansionWorker)); - instance->thread = - furi_thread_alloc_ex(TAG, EXPANSION_WORKER_STACK_SZIE, expansion_worker, instance); + instance->thread = furi_thread_alloc_ex( + TAG "Worker", EXPANSION_WORKER_STACK_SZIE, expansion_worker, instance); instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_WORKER_BUFFER_SIZE, 1); instance->serial_id = serial_id; diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h index 0d497ff81..761f79c1d 100644 --- a/applications/services/expansion/expansion_worker.h +++ b/applications/services/expansion/expansion_worker.h @@ -1,20 +1,78 @@ +/** + * @file expansion_worker.h + * @brief Expansion module handling thread wrapper. + * + * The worker is started each time an expansion module is detected + * and handles all of the communication protocols. Likewise, it is stopped + * upon module disconnection or communication error. + * + * @warning This file is a private implementation detail. Please do not attempt to use it in applications. + */ #pragma once #include +/** + * @brief Expansion worker opaque type declaration. + */ typedef struct ExpansionWorker ExpansionWorker; -typedef void (*ExpansionWorkerCallback)(void*); +/** + * @brief Worker callback type. + * + * @see expansion_worker_set_callback() + * + * @param[in,out] context pointer to a user-defined object. + */ +typedef void (*ExpansionWorkerCallback)(void* context); +/** + * @brief Create an expansion worker instance. + * + * @param[in] serial_id numerical identifier of the serial to be used by the worker. + * @returns pointer to the created instance. + */ ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id); +/** + * @brief Delete an expansion worker instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ void expansion_worker_free(ExpansionWorker* instance); +/** + * @brief Set the module disconnect callback. + * + * The callback will be triggered upon worker stop EXCEPT + * when it was stopped via an expansion_worker_stop() call. + * + * In other words, the callback will ONLY be triggered if the worker was + * stopped due to the user disconnecting/resetting/powering down the module, + * or due to some communication error. + * + * @param[in,out] instance pointer to the worker instance to be modified. + * @param[in] callback pointer to the callback function to be called under the above conditions. + * @param[in] context pointer to a user-defined object, will be passed as a parameter to the callback. + */ void expansion_worker_set_callback( ExpansionWorker* instance, ExpansionWorkerCallback callback, void* context); +/** + * @brief Start the expansion module worker. + * + * @param[in,out] instance pointer to the worker instance to be started. + */ void expansion_worker_start(ExpansionWorker* instance); +/** + * @brief Stop the expansion module worker. + * + * If the worker was stopped via this call (and not because of module disconnect/ + * protocol error), the callback will not be triggered. + * + * @param[in,out] instance pointer to the worker instance to be stopped. + */ void expansion_worker_stop(ExpansionWorker* instance); From ff28c23f64e25185da7db609756ff744e218716a Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 17:10:14 +0300 Subject: [PATCH 06/36] Fix crash when disabling expansion modules --- applications/services/expansion/expansion.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 0b28f5a49..abfd1971b 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -107,15 +107,14 @@ static void if(instance->state == ExpansionStateDisabled) { return; - } - - if(instance->state == ExpansionStateRunning) { + } else if(instance->state == ExpansionStateRunning) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); + } else { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); } instance->state = ExpansionStateDisabled; - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); FURI_LOG_D(TAG, "Detection disabled"); } From 2e05489a8272602c36cdeba397a377e52e2ac318 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 18:12:13 +0300 Subject: [PATCH 07/36] Show a different RPC icon for expansion modules --- applications/services/rpc/rpc_gui.c | 38 ++++++++++++------ .../StatusBar/Exp_module_connected_12x8.png | Bin 0 -> 3612 bytes 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 assets/icons/StatusBar/Exp_module_connected_12x8.png diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 98860332d..ee3526590 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -54,6 +54,7 @@ typedef enum { typedef struct { RpcSession* session; Gui* gui; + const Icon* icon; // Receive part ViewPort* virtual_display_view_port; @@ -380,10 +381,19 @@ static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, (void)session; } +static const Icon* rpc_system_gui_get_owner_icon(RpcOwner owner) { + switch(owner) { + case RpcOwnerUart: + return &I_Exp_module_connected_12x8; + default: + return &I_Rpc_active_7x8; + } +} + static void rpc_active_session_icon_draw_callback(Canvas* canvas, void* context) { - UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Rpc_active_7x8); + RpcGuiSystem* rpc_gui = context; + canvas_draw_icon(canvas, 0, 0, rpc_gui->icon); } void* rpc_system_gui_alloc(RpcSession* session) { @@ -394,16 +404,16 @@ void* rpc_system_gui_alloc(RpcSession* session) { rpc_gui->session = session; // Active session icon - rpc_gui->rpc_session_active_viewport = view_port_alloc(); - view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(&I_Rpc_active_7x8)); - view_port_draw_callback_set( - rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, session); - if(rpc_session_get_owner(rpc_gui->session) != RpcOwnerBle) { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, true); - } else { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, false); + const RpcOwner owner = rpc_session_get_owner(rpc_gui->session); + if(owner != RpcOwnerBle) { + rpc_gui->icon = rpc_system_gui_get_owner_icon(owner); + rpc_gui->rpc_session_active_viewport = view_port_alloc(); + view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(rpc_gui->icon)); + view_port_draw_callback_set( + rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, rpc_gui); + gui_add_view_port( + rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); } - gui_add_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); RpcHandler rpc_handler = { .message_handler = NULL, @@ -445,8 +455,10 @@ void rpc_system_gui_free(void* context) { rpc_gui->virtual_display_not_empty = false; } - gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); - view_port_free(rpc_gui->rpc_session_active_viewport); + if(rpc_gui->rpc_session_active_viewport) { + gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); + view_port_free(rpc_gui->rpc_session_active_viewport); + } if(rpc_gui->is_streaming) { rpc_gui->is_streaming = false; diff --git a/assets/icons/StatusBar/Exp_module_connected_12x8.png b/assets/icons/StatusBar/Exp_module_connected_12x8.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f09668267a55680aaa66355966bdaddcc173e1 GIT binary patch literal 3612 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 literal 0 HcmV?d00001 From 962a7b1ba70d72664120fa84b5049b38c738a717 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Tue, 6 Feb 2024 19:40:56 +0300 Subject: [PATCH 08/36] Restore expansion interrupt on changing logging settings --- .../services/expansion/application.fam | 2 +- targets/f7/furi_hal/furi_hal_serial_control.c | 61 +++++++++++++------ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/applications/services/expansion/application.fam b/applications/services/expansion/application.fam index 1402e8413..dbdde0a52 100644 --- a/applications/services/expansion/application.fam +++ b/applications/services/expansion/application.fam @@ -8,5 +8,5 @@ App( ], requires=["rpc_start"], provides=["expansion_settings"], - order=10, + order=150, ) diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index 37454823b..11ef23a6f 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -47,6 +47,7 @@ typedef struct { FuriHalSerialHandle* log_serial; // Expansion detection + FuriHalSerialHandle* expansion_serial; FuriHalSerialControlExpansionCallback expansion_cb; void* expansion_ctx; } FuriHalSerialControl; @@ -58,7 +59,36 @@ static void furi_hal_serial_control_log_callback(const uint8_t* data, size_t siz furi_hal_serial_tx(handle, data, size); } +static void furi_hal_serial_control_expansion_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeExpansionIrq; + message.api_lock = NULL; + furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); +} + +static void + furi_hal_serial_control_enable_expansion_irq(FuriHalSerialHandle* handle, bool enable) { + const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); + + if(enable) { + furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); + furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + } else { + furi_hal_gpio_remove_int_callback(gpio); + furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + } +} + static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) { + // Disable expansion module detection before reconfiguring UARTs + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, false); + } + if(furi_hal_serial_control->log_serial) { furi_log_remove_handler(furi_hal_serial_control->log_handler); furi_hal_serial_deinit(furi_hal_serial_control->log_serial); @@ -74,15 +104,12 @@ static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) furi_hal_serial_control->log_handler.context = furi_hal_serial_control->log_serial; furi_log_add_handler(furi_hal_serial_control->log_handler); } -} -static void furi_hal_serial_control_expansion_irq_callback(void* context) { - UNUSED(context); - - FuriHalSerialControlMessage message; - message.type = FuriHalSerialControlMessageTypeExpansionIrq; - message.api_lock = NULL; - furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); + // Re-enable expansion module detection (if applicable) + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, true); + } } static bool furi_hal_serial_control_handler_stop(void* input, void* output) { @@ -157,24 +184,24 @@ static bool furi_hal_serial_control_handler_expansion_set_callback(void* input, FuriHalSerialControlMessageExpCallback* message_input = input; FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[message_input->id]; - const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); - if(message_input->callback) { + const bool enable_irq = message_input->callback != NULL; + + if(enable_irq) { + furi_check(furi_hal_serial_control->expansion_serial == NULL); furi_check(furi_hal_serial_control->expansion_cb == NULL); - - furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); - furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); - furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + furi_hal_serial_control->expansion_serial = handle; } else { + furi_check(furi_hal_serial_control->expansion_serial == handle); furi_check(furi_hal_serial_control->expansion_cb != NULL); - - furi_hal_gpio_remove_int_callback(gpio); - furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_serial_control->expansion_serial = NULL; } furi_hal_serial_control->expansion_cb = message_input->callback; furi_hal_serial_control->expansion_ctx = message_input->context; + furi_hal_serial_control_enable_expansion_irq(handle, enable_irq); + return true; } From 29fa68118d827e951ea1c9139e470838cf8d090e Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Wed, 7 Feb 2024 12:38:20 +1000 Subject: [PATCH 09/36] GUI: canvas commit callback has been moved to canvas. Direct Draw apps can now be streamed via RPC. (#3397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/canvas.c | 55 ++++++++++++++++++++++++++++ applications/services/gui/canvas_i.h | 51 ++++++++++++++++++++++++++ applications/services/gui/gui.c | 27 ++------------ applications/services/gui/gui_i.h | 13 ------- 4 files changed, 110 insertions(+), 36 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 44adcd939..897f0ae13 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -18,6 +18,12 @@ Canvas* canvas_init() { Canvas* canvas = malloc(sizeof(Canvas)); canvas->compress_icon = compress_icon_alloc(); + // Initialize mutex + canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + // Initialize callback array + CanvasCallbackPairArray_init(canvas->canvas_callback_pair); + // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); canvas->orientation = CanvasOrientationHorizontal; @@ -36,9 +42,21 @@ Canvas* canvas_init() { void canvas_free(Canvas* canvas) { furi_assert(canvas); compress_icon_free(canvas->compress_icon); + CanvasCallbackPairArray_clear(canvas->canvas_callback_pair); + furi_mutex_free(canvas->mutex); free(canvas); } +static void canvas_lock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_acquire(canvas->mutex, FuriWaitForever) == FuriStatusOk); +} + +static void canvas_unlock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_release(canvas->mutex) == FuriStatusOk); +} + void canvas_reset(Canvas* canvas) { furi_assert(canvas); @@ -52,6 +70,18 @@ void canvas_reset(Canvas* canvas) { void canvas_commit(Canvas* canvas) { furi_assert(canvas); u8g2_SendBuffer(&canvas->fb); + + // Iterate over callbacks + canvas_lock(canvas); + for + M_EACH(p, canvas->canvas_callback_pair, CanvasCallbackPairArray_t) { + p->callback( + canvas_get_buffer(canvas), + canvas_get_buffer_size(canvas), + canvas_get_orientation(canvas), + p->context); + } + canvas_unlock(canvas); } uint8_t* canvas_get_buffer(Canvas* canvas) { @@ -542,3 +572,28 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { CanvasOrientation canvas_get_orientation(const Canvas* canvas) { return canvas->orientation; } + +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(!CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p)); + CanvasCallbackPairArray_push_back(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} + +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p) == 1); + CanvasCallbackPairArray_remove_val(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} \ No newline at end of file diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index f3b8f17ad..73502b719 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -8,6 +8,30 @@ #include "canvas.h" #include #include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*CanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); + +typedef struct { + CanvasCommitCallback callback; + void* context; +} CanvasCallbackPair; + +ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); + +#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) + +ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); /** Canvas structure */ @@ -19,6 +43,8 @@ struct Canvas { uint8_t width; uint8_t height; CompressIcon* compress_icon; + CanvasCallbackPairArray_t canvas_callback_pair; + FuriMutex* mutex; }; /** Allocate memory and initialize canvas @@ -97,3 +123,28 @@ void canvas_draw_u8g2_bitmap( uint8_t height, const uint8_t* bitmap, uint8_t rotation); + +/** Add canvas commit callback. + * + * This callback will be called upon Canvas commit. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context); + +/** Remove canvas commit callback. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 0bdc999b7..9deaf23fe 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -265,14 +265,6 @@ static void gui_redraw(Gui* gui) { } canvas_commit(gui->canvas); - for - M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { - p->callback( - canvas_get_buffer(gui->canvas), - canvas_get_buffer_size(gui->canvas), - canvas_get_orientation(gui->canvas), - p->context); - } } while(false); gui_unlock(gui); @@ -473,12 +465,7 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(!CanvasCallbackPairArray_count(gui->canvas_callback_pair, p)); - CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_add_framebuffer_callback(gui->canvas, callback, context); // Request redraw gui_update(gui); @@ -487,12 +474,7 @@ void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 1); - CanvasCallbackPairArray_remove_val(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_remove_framebuffer_callback(gui->canvas, callback, context); } size_t gui_get_framebuffer_size(const Gui* gui) { @@ -542,20 +524,19 @@ Gui* gui_alloc() { gui->thread_id = furi_thread_get_current_id(); // Allocate mutex gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - furi_check(gui->mutex); + // Layers for(size_t i = 0; i < GuiLayerMAX; i++) { ViewPortArray_init(gui->layers[i]); } + // Drawing canvas gui->canvas = canvas_init(); - CanvasCallbackPairArray_init(gui->canvas_callback_pair); // Input gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); gui->input_events = furi_record_open(RECORD_INPUT_EVENTS); - furi_check(gui->input_events); furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); return gui; diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index a5e269e03..3ca9c05c9 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include "canvas.h" @@ -44,17 +43,6 @@ ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); -typedef struct { - GuiCanvasCommitCallback callback; - void* context; -} CanvasCallbackPair; - -ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); - -#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) - -ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); - /** Gui structure */ struct Gui { // Thread and lock @@ -66,7 +54,6 @@ struct Gui { bool direct_draw; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; - CanvasCallbackPairArray_t canvas_callback_pair; // Input FuriMessageQueue* input_queue; From 4265b9c28ee569ec7fb674e6af3a85ec3599865a Mon Sep 17 00:00:00 2001 From: Methodius Date: Wed, 7 Feb 2024 14:21:58 +0900 Subject: [PATCH 10/36] parsers cleanup --- .../main/nfc/plugins/supported_cards/aime.c | 2 +- .../main/nfc/plugins/supported_cards/hid.c | 2 +- .../main/nfc/plugins/supported_cards/kazan.c | 6 ++-- .../nfc/plugins/supported_cards/metromoney.c | 4 +-- .../main/nfc/plugins/supported_cards/saflok.c | 4 +-- .../plugins/supported_cards/social_moscow.c | 4 +-- .../main/nfc/plugins/supported_cards/troika.c | 4 +-- .../nfc/plugins/supported_cards/washcity.c | 4 +-- .../plugins/supported_cards/zolotaya_korona.c | 35 ++----------------- 9 files changed, 17 insertions(+), 48 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c index d9759421a..c747a70b9 100644 --- a/applications/main/nfc/plugins/supported_cards/aime.c +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -67,7 +67,7 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) { nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c index 0622e3dc1..cb0178ecc 100644 --- a/applications/main/nfc/plugins/supported_cards/hid.c +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -67,7 +67,7 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) { nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 05acc4a99..841d126d1 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -214,14 +214,14 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys_v1, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v1"); break; } if(!mf_classic_is_card_read(data)) { error = mf_classic_poller_sync_read(nfc, &keys_v2, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v1"); break; } @@ -229,7 +229,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { if(!mf_classic_is_card_read(data)) { error = mf_classic_poller_sync_read(nfc, &keys_v3, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v3"); break; } diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index 2b33da153..34855bcb5 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -107,14 +107,14 @@ static bool metromoney_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index 2f3523caa..4b59e853e 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -137,14 +137,14 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index c272b8062..321526320 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -1534,14 +1534,14 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 2bc4ba579..22e262241 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1559,14 +1559,14 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 86ea91f99..874337ec7 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -108,14 +108,14 @@ static bool washcity_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index 1778cd38e..2965e7e4f 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -40,37 +40,6 @@ static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x 0xAE, 0xE0, 0xAE, 0xAD, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 -#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) -#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) -#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 - -void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { - uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; - uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; - - datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; - - while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { - days -= furi_hal_rtc_get_days_per_year(datetime->year); - (datetime->year)++; - } - - datetime->month = 1; - while(days >= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month)) { - days -= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month); - (datetime->month)++; - } - - datetime->day = days + 1; - datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; - datetime->minute = - (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; - datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; -} - uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) { furi_assert(src); furi_assert(len_bytes <= 9); @@ -151,7 +120,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da const uint16_t refill_counter = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 2); FuriHalRtcDateTime last_refill_datetime = {0}; - timestamp_to_datetime(last_refill_timestamp, &last_refill_datetime); + furi_hal_rtc_timestamp_to_datetime(last_refill_timestamp, &last_refill_datetime); // block 2: trip block block_start_ptr = &data->block[start_trip_block_number + 2].data[0]; @@ -166,7 +135,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da const uint8_t prev_balance_kop = prev_balance % 100; FuriHalRtcDateTime last_trip_datetime = {0}; - timestamp_to_datetime(last_trip_timestamp, &last_trip_datetime); + furi_hal_rtc_timestamp_to_datetime(last_trip_timestamp, &last_trip_datetime); // PARSE DATA FROM PURSE SECTOR const uint8_t start_purse_block_number = From fee6f126842473eed8ee991264ad26bfbea360b1 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 7 Feb 2024 11:43:28 +0300 Subject: [PATCH 11/36] Improve responsiveness in heavy games at the expense of dropped frames --- applications/services/expansion/expansion_worker.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index eef51d2f6..70b2a1103 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -372,6 +372,9 @@ ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_WORKER_BUFFER_SIZE, 1); instance->serial_id = serial_id; + // Improves responsiveness in heavy games at the expense of dropped frames + furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); + return instance; } From afd41aade1bc1a9a8de23e7754ef4e8c441c0865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Feb 2024 16:09:29 +0700 Subject: [PATCH 12/36] FuriHal: various GPIO improvements (#3406) --- furi/core/common_defines.h | 4 ++++ targets/f7/furi_hal/furi_hal_gpio.c | 32 +++++++++++++++++++++-------- targets/f7/furi_hal/furi_hal_gpio.h | 1 - 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index b0062e659..2fc12dedb 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -21,6 +21,10 @@ extern "C" { #define FURI_PACKED __attribute__((packed)) #endif +#ifndef FURI_ALWAYS_STATIC_INLINE +#define FURI_ALWAYS_STATIC_INLINE __attribute__((always_inline)) static inline +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index d5221aba9..1e703b5d1 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -129,11 +129,9 @@ void furi_hal_gpio_init_ex( LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT); LL_SYSCFG_SetEXTISource(sys_exti_port, sys_exti_line); if(mode == GpioModeInterruptRise || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableRisingTrig_0_31(exti_line); } if(mode == GpioModeInterruptFall || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableFallingTrig_0_31(exti_line); } if(mode == GpioModeEventRise || mode == GpioModeEventRiseFall) { @@ -149,6 +147,7 @@ void furi_hal_gpio_init_ex( if(LL_SYSCFG_GetEXTISource(sys_exti_line) == sys_exti_port && LL_EXTI_IsEnabledIT_0_31(exti_line)) { LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); LL_EXTI_DisableRisingTrig_0_31(exti_line); LL_EXTI_DisableFallingTrig_0_31(exti_line); } @@ -199,11 +198,15 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo furi_assert(cb); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); furi_check(gpio_interrupt[pin_num].callback == NULL); gpio_interrupt[pin_num].callback = cb; gpio_interrupt[pin_num].context = ctx; - gpio_interrupt[pin_num].ready = true; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -211,10 +214,13 @@ void furi_hal_gpio_enable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); if(gpio_interrupt[pin_num].callback) { - gpio_interrupt[pin_num].ready = true; + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); } + FURI_CRITICAL_EXIT(); } @@ -222,8 +228,11 @@ void furi_hal_gpio_disable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); - uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - gpio_interrupt[pin_num].ready = false; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -231,15 +240,20 @@ void furi_hal_gpio_remove_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); gpio_interrupt[pin_num].callback = NULL; gpio_interrupt[pin_num].context = NULL; - gpio_interrupt[pin_num].ready = false; + FURI_CRITICAL_EXIT(); } -static void furi_hal_gpio_int_call(uint16_t pin_num) { - if(gpio_interrupt[pin_num].callback && gpio_interrupt[pin_num].ready) { +FURI_ALWAYS_STATIC_INLINE void furi_hal_gpio_int_call(uint16_t pin_num) { + if(gpio_interrupt[pin_num].callback) { gpio_interrupt[pin_num].callback(gpio_interrupt[pin_num].context); } } diff --git a/targets/f7/furi_hal/furi_hal_gpio.h b/targets/f7/furi_hal/furi_hal_gpio.h index 0999971bc..9e78872bb 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.h +++ b/targets/f7/furi_hal/furi_hal_gpio.h @@ -24,7 +24,6 @@ typedef void (*GpioExtiCallback)(void* ctx); typedef struct { GpioExtiCallback callback; void* context; - volatile bool ready; } GpioInterrupt; /** From c489c956e7595a1bc2b0ce1b973fe2ea2eb5458e Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 7 Feb 2024 21:13:38 +0300 Subject: [PATCH 13/36] Improve furi_hal_serial API --- applications/services/expansion/expansion_worker.c | 13 +++++++++---- applications/services/rpc/rpc.c | 6 ++++++ targets/f18/api_symbols.csv | 3 ++- targets/f7/api_symbols.csv | 3 ++- targets/f7/furi_hal/furi_hal_serial.c | 11 +++++++++++ targets/f7/furi_hal/furi_hal_serial.h | 10 ++++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index 70b2a1103..c2219a40d 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -61,9 +61,14 @@ static void expansion_worker_serial_rx_callback( ExpansionWorker* instance = context; - if(event == FuriHalSerialRxEventData) { - const uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + if(event & (FuriHalSerialRxEventNoiseError | FuriHalSerialRxEventFrameError | + FuriHalSerialRxEventOverrunError)) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + } else if(event & FuriHalSerialRxEventData) { + while(furi_hal_serial_async_rx_available(handle)) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + } furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagData); } } @@ -341,7 +346,7 @@ static int32_t expansion_worker(void* context) { furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); furi_hal_serial_async_rx_start( - instance->serial_handle, expansion_worker_serial_rx_callback, instance, false); + instance->serial_handle, expansion_worker_serial_rx_callback, instance, true); if(expansion_worker_send_heartbeat(instance)) { expansion_worker_state_machine(instance); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 3179dbb55..d33ea178c 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -189,6 +189,12 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); + /* TODO FL-3768 this function may be called after + marking the worker for termination */ + if(session->terminate) { + return false; + } + uint32_t flags = 0; size_t bytes_received = 0; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ffb664a3e..4994fc475 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,v,54.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1273,6 +1273,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f852a69be..5cbced47f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,+,54.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1439,6 +1439,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 1296ee620..24075223e 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -782,6 +782,17 @@ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle) { furi_hal_serial_async_rx_configure(handle, NULL, NULL); } +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle) { + furi_check(FURI_IS_IRQ_MODE()); + furi_assert(handle->id < FuriHalSerialIdMax); + + if(handle->id == FuriHalSerialIdUsart) { + return LL_USART_IsActiveFlag_RXNE_RXFNE(USART1); + } else { + return LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1); + } +} + uint8_t furi_hal_serial_async_rx(FuriHalSerialHandle* handle) { furi_check(FURI_IS_IRQ_MODE()); furi_assert(handle->id < FuriHalSerialIdMax); diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 975406670..00010d83c 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -130,6 +130,16 @@ void furi_hal_serial_async_rx_start( */ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle); +/** Check if there is data available for reading + * + * @warning This function must be called only from the callback + * FuriHalSerialAsyncRxCallback + * + * @param handle Serial handle + * @return true if data is available for reading, false otherwise + */ +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle); + /** Get data Serial receive * * @warning This function must be called only from the callback From 5a92adaefc5295111d8990b7cbb8ae8f2ea8a4d7 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 7 Feb 2024 21:16:04 +0300 Subject: [PATCH 14/36] Fix a typo --- targets/f18/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 4994fc475..1971f5c41 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,v,54.1,, +Version,+,54.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, From 26da5f564b7f72f71b61f4a74f4638be8d435323 Mon Sep 17 00:00:00 2001 From: Kris Bahnsen Date: Thu, 8 Feb 2024 01:22:03 -0800 Subject: [PATCH 15/36] furi/core/timer: resolve timer handle use-after-free post deletion (#3431) When xTimerDelete is called using a dymanic timer handle, the timer handle should immediately be considered unusable for any operation; including checking if the timer is still running. Under high system loads, that memory region may see fast reuse while furi_timer_free is sleeping between timer active checks. That reuse could result in memory at that pointer causing the timer active check to return true. Rework the furi_timer_delete process (in the case of dynamically allocated callback memory) to stop the timer, wait for it to stop, free the memory, and then delete the timer. Timers without dynamically allocated callback memory are just sent a delete command; no need to stop it first. Fixes: ff33bc6aea43 ("Furi: wait for timer wind down in destructor (#1716)") Signed-off-by: Kris Bahnsen --- furi/core/timer.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/furi/core/timer.c b/furi/core/timer.c index 027e4cf40..f667aae96 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -67,17 +67,22 @@ void furi_timer_free(FuriTimer* instance) { callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - - while(furi_timer_is_running(instance)) furi_delay_tick(2); - if((uint32_t)callb & 1U) { + /* If callback memory was allocated, it is only safe to free it with + * the timer inactive. Send a stop command and wait for the timer to + * be in an inactive state. + */ + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + while(furi_timer_is_running(instance)) furi_delay_tick(2); + /* Callback memory was allocated from dynamic pool, clear flag */ callb = (TimerCallback_t*)((uint32_t)callb & ~1U); /* Return allocated memory to dynamic pool */ free(callb); } + + furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -170,4 +175,4 @@ void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { } else { furi_crash(); } -} \ No newline at end of file +} From 534fe2fb263177b414d0381b4ee52e5d99feb341 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Thu, 8 Feb 2024 14:50:14 +0300 Subject: [PATCH 16/36] Remove too optimistic furi_check, replace with condition --- applications/services/expansion/expansion.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index abfd1971b..99cdd984f 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -144,10 +144,8 @@ static void expansion_control_handler_set_listen_serial( static void expansion_control_handler_module_connected( Expansion* instance, const ExpansionMessageData* data) { - furi_check(instance->state != ExpansionStateDisabled); UNUSED(data); - - if(instance->state == ExpansionStateRunning) { + if(instance->state != ExpansionStateEnabled) { return; } @@ -164,15 +162,14 @@ static void expansion_control_handler_module_disconnected( Expansion* instance, const ExpansionMessageData* data) { UNUSED(data); - // This condition should be always true, but in some rare edge cases - // it is possible to change the settings while the module was being - // disconnected, hence the additional check. - if(instance->state == ExpansionStateRunning) { - instance->state = ExpansionStateEnabled; - expansion_worker_free(instance->worker); - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); + if(instance->state != ExpansionStateRunning) { + return; } + + instance->state = ExpansionStateEnabled; + expansion_worker_free(instance->worker); + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); } typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); From ded9b121ba8c832b5317e3451a570f6e93a4ee41 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Thu, 8 Feb 2024 15:18:58 +0300 Subject: [PATCH 17/36] Fix premature RX interrupt during serial configuration --- targets/f7/furi_hal/furi_hal_serial.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 24075223e..e0e2d8d52 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -730,6 +730,13 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle + furi_hal_serial[handle->id].rx_byte_callback = callback; + furi_hal_serial[handle->id].handle = handle; + furi_hal_serial[handle->id].rx_dma_callback = NULL; + furi_hal_serial[handle->id].context = context; + if(handle->id == FuriHalSerialIdUsart) { if(callback) { furi_hal_serial_usart_deinit_dma_rx(); @@ -753,10 +760,6 @@ static void furi_hal_serial_async_rx_configure( LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } - furi_hal_serial[handle->id].rx_byte_callback = callback; - furi_hal_serial[handle->id].handle = handle; - furi_hal_serial[handle->id].rx_dma_callback = NULL; - furi_hal_serial[handle->id].context = context; } void furi_hal_serial_async_rx_start( From f6eb79e1e5b850a8e107462ee47f68803e98602a Mon Sep 17 00:00:00 2001 From: Nick Mooney Date: Fri, 9 Feb 2024 21:00:17 +1300 Subject: [PATCH 18/36] NFC: Add support for Gallagher access control (MIFARE Classic only) (#3306) * Merge remote-tracking branch 'origin/dev' into dev * Add basic API interface (inspired by advanced plugins example), modify NfcSupportedCardsLoadContext to contain a pointer to a resolver * WIP: API resolver implemented / passed to plugin via context, still having resolution issues * Attempt to add constants to the nfc_app_api_table list * WIP: We're defining the constants directly in nfc_app_api.c to see if this fixes our woes, which it does not. * WIP: Remove furi_assert(false) (lmao) * Working implementation of Gallagher decoding via exposed plugin API * lib: api_hashtable: change log level for symbol not found message * nfc app: alloc composite resolver along with supported cards * nfc app: rework nfc api structure * nfc app: fix memory leakage in supported cards Co-authored-by: gornekich --- .../main/nfc/api/gallagher/gallagher_util.c | 59 +++++++++++++ .../main/nfc/api/gallagher/gallagher_util.h | 33 +++++++ .../main/nfc/api/nfc_app_api_interface.h | 9 ++ .../main/nfc/api/nfc_app_api_table.cpp | 27 ++++++ .../main/nfc/api/nfc_app_api_table_i.h | 13 +++ applications/main/nfc/application.fam | 11 ++- .../main/nfc/helpers/nfc_supported_cards.c | 42 ++++++--- .../nfc/plugins/supported_cards/gallagher.c | 87 +++++++++++++++++++ .../api_hashtable/api_hashtable.cpp | 2 +- 9 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.c create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.h create mode 100644 applications/main/nfc/api/nfc_app_api_interface.h create mode 100644 applications/main/nfc/api/nfc_app_api_table.cpp create mode 100644 applications/main/nfc/api/nfc_app_api_table_i.h create mode 100644 applications/main/nfc/plugins/supported_cards/gallagher.c diff --git a/applications/main/nfc/api/gallagher/gallagher_util.c b/applications/main/nfc/api/gallagher/gallagher_util.c new file mode 100644 index 000000000..caa3650e7 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.c @@ -0,0 +1,59 @@ +/* gallagher_util.c - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "gallagher_util.h" + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +/* The Gallagher obfuscation algorithm is a 256-byte substitution table. The below array is generated from + * https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/substitution-table.bin. +*/ +const uint8_t GALLAGHER_DECODE_TABLE[256] = { + 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0xf, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, + 0x6d, 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, + 0x62, 0x1a, 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, + 0xae, 0x1f, 0x9b, 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, + 0xe, 0xa9, 0xe2, 0x8d, 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, + 0xe8, 0xa6, 0x20, 0xab, 0x87, 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0xc, 0xc3, 0x3c, 0x61, 0xcc, + 0x40, 0x9e, 0x6, 0x52, 0x1b, 0x32, 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0xd, 0xc2, + 0x88, 0xd1, 0xe0, 0x7, 0x2d, 0x70, 0xc6, 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, + 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, 0x2, 0x8, 0x30, 0xa7, 0x22, 0xc9, 0x65, + 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, + 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, 0x51, 0xd4, 0x81, 0x0, 0x2e, + 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, 0x1, 0x48, 0x4, 0xc1, + 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0xa, 0xac, 0x18, 0x4b, 0xcd, 0x98, 0xb8, 0x37, + 0xa2, 0x83, 0xec, 0x3, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, 0x43, 0xe9, + 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, 0x89, + 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0xb, 0xf1, 0xea, 0xfd, 0xdb, + 0xbd, 0x9, 0xb5, 0x5b, 0x5, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, + 0x36}; + +// The second block of a Gallagher credential sector is the literal +// "www.cardax.com " (note two padding spaces) +const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE] = + {'w', 'w', 'w', '.', 'c', 'a', 'r', 'd', 'a', 'x', '.', 'c', 'o', 'm', ' ', ' '}; + +/* Precondition: cardholder_data_obfuscated points to at least 8 safe-to-read bytes of memory. +*/ +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated) { + uint8_t cardholder_data_deobfuscated[8]; + for(int i = 0; i < 8; i++) { + cardholder_data_deobfuscated[i] = GALLAGHER_DECODE_TABLE[cardholder_data_obfuscated[i]]; + } + + // Pull out values from the deobfuscated data + credential->region = (cardholder_data_deobfuscated[3] >> 1) & 0x0F; + credential->facility = ((uint16_t)(cardholder_data_deobfuscated[5] & 0x0F) << 12) + + ((uint16_t)cardholder_data_deobfuscated[1] << 4) + + (((uint16_t)cardholder_data_deobfuscated[7] >> 4) & 0x0F); + credential->card = ((uint32_t)cardholder_data_deobfuscated[0] << 16) + + ((uint32_t)(cardholder_data_deobfuscated[4] & 0x1F) << 11) + + ((uint32_t)cardholder_data_deobfuscated[2] << 3) + + (((uint32_t)cardholder_data_deobfuscated[3] >> 5) & 0x07); + credential->issue = cardholder_data_deobfuscated[7] & 0x0F; +} \ No newline at end of file diff --git a/applications/main/nfc/api/gallagher/gallagher_util.h b/applications/main/nfc/api/gallagher/gallagher_util.h new file mode 100644 index 000000000..79e098389 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.h @@ -0,0 +1,33 @@ +/* gallagher_util.h - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#pragma once + +#include + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t GALLAGHER_DECODE_TABLE[256]; +extern const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE]; + +typedef struct GallagherCredential { + uint8_t region; + uint8_t issue; + uint16_t facility; + uint32_t card; +} GallagherCredential; + +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/nfc/api/nfc_app_api_interface.h b/applications/main/nfc/api/nfc_app_api_interface.h new file mode 100644 index 000000000..162355982 --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const nfc_application_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table.cpp b/applications/main/nfc/api/nfc_app_api_table.cpp new file mode 100644 index 000000000..ca190665b --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "nfc_app_api_table_i.h" + +static_assert(!has_hash_collisions(nfc_app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface nfc_application_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = nfc_app_api_table.cbegin(), + .table_cend = nfc_app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const nfc_application_api_interface = + &nfc_application_hashtable_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h new file mode 100644 index 000000000..08bff072e --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -0,0 +1,13 @@ +#include "gallagher/gallagher_util.h" + +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto nfc_app_api_table = sort(create_array_t( + API_METHOD( + gallagher_deobfuscate_and_parse_credential, + void, + (GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)), + API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index c0acace5d..1e6291c4d 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -9,7 +9,7 @@ App( order=30, resources="resources", sources=[ - "*.c", + "*.c*", "!plugins", "!nfc_cli.c", ], @@ -110,6 +110,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="gallagher_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="gallagher_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/gallagher.c"], +) + App( appid="clipper_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6016ae178..f5668b506 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -1,9 +1,11 @@ #include "nfc_supported_cards.h" +#include "../api/nfc_app_api_interface.h" #include "../plugins/supported_cards/nfc_supported_card_plugin.h" #include #include +#include #include #include @@ -45,6 +47,7 @@ typedef struct { } NfcSupportedCardsLoadContext; struct NfcSupportedCards { + CompositeApiResolver* api_resolver; NfcSupportedCardsPluginCache_t plugins_cache_arr; NfcSupportedCardsLoadState load_state; NfcSupportedCardsLoadContext* load_context; @@ -52,6 +55,11 @@ struct NfcSupportedCards { NfcSupportedCards* nfc_supported_cards_alloc() { NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); + + instance->api_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->api_resolver, firmware_api_interface); + composite_api_resolver_add(instance->api_resolver, nfc_application_api_interface); + NfcSupportedCardsPluginCache_init(instance->plugins_cache_arr); return instance; @@ -67,8 +75,9 @@ void nfc_supported_cards_free(NfcSupportedCards* instance) { NfcSupportedCardsPluginCache* plugin_cache = NfcSupportedCardsPluginCache_ref(iter); furi_string_free(plugin_cache->path); } - NfcSupportedCardsPluginCache_clear(instance->plugins_cache_arr); + + composite_api_resolver_free(instance->api_resolver); free(instance); } @@ -100,15 +109,17 @@ static void nfc_supported_cards_load_context_free(NfcSupportedCardsLoadContext* free(instance); } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_plugin(NfcSupportedCardsLoadContext* instance, FuriString* path) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_plugin( + NfcSupportedCardsLoadContext* instance, + const FuriString* path, + const ElfApiInterface* api_interface) { furi_assert(instance); furi_assert(path); const NfcSupportedCardsPlugin* plugin = NULL; do { if(instance->app) flipper_application_free(instance->app); - instance->app = flipper_application_alloc(instance->storage, firmware_api_interface); + instance->app = flipper_application_alloc(instance->storage, api_interface); if(flipper_application_preload(instance->app, furi_string_get_cstr(path)) != FlipperApplicationPreloadStatusSuccess) break; @@ -129,8 +140,9 @@ static const NfcSupportedCardsPlugin* return plugin; } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_next_plugin(NfcSupportedCardsLoadContext* instance) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_next_plugin( + NfcSupportedCardsLoadContext* instance, + const ElfApiInterface* api_interface) { const NfcSupportedCardsPlugin* plugin = NULL; do { @@ -145,7 +157,7 @@ static const NfcSupportedCardsPlugin* path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path); - plugin = nfc_supported_cards_get_plugin(instance, instance->file_path); + plugin = nfc_supported_cards_get_plugin(instance, instance->file_path, api_interface); } while(plugin == NULL); //-V654 return plugin; @@ -162,8 +174,10 @@ void nfc_supported_cards_load_cache(NfcSupportedCards* instance) { instance->load_context = nfc_supported_cards_load_context_alloc(); while(true) { + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_next_plugin(instance->load_context); + nfc_supported_cards_get_next_plugin(instance->load_context, api_interface); if(plugin == NULL) break; //-V547 NfcSupportedCardsPluginCache plugin_cache = {}; //-V779 @@ -216,8 +230,10 @@ bool nfc_supported_cards_read(NfcSupportedCards* instance, NfcDevice* device, Nf if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasRead) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->verify) { @@ -262,8 +278,10 @@ bool nfc_supported_cards_parse( if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasParse) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->parse) { diff --git a/applications/main/nfc/plugins/supported_cards/gallagher.c b/applications/main/nfc/plugins/supported_cards/gallagher.c new file mode 100644 index 000000000..8b6a58987 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/gallagher.c @@ -0,0 +1,87 @@ +/* gallagher.c - NFC supported cards plugin for Gallagher access control cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "nfc_supported_card_plugin.h" +#include "../../api/gallagher/gallagher_util.h" + +#include +#include +#include +#include + +static bool gallagher_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + if(!(data->type == MfClassicType1k || data->type == MfClassicType4k)) { + return false; + } + + // It's possible for a single tag to contain multiple credentials, + // but this is currently unimplementecd. + const uint8_t credential_sector_start_block_number = + mf_classic_get_first_block_num_of_sector(GALLAGHER_CREDENTIAL_SECTOR); + + // Test 1: The first 8 bytes and the second 8 bytes should be bitwise inverses. + const uint8_t* credential_block_start_ptr = + &data->block[credential_sector_start_block_number].data[0]; + uint64_t cardholder_credential = nfc_util_bytes2num(credential_block_start_ptr, 8); + uint64_t cardholder_credential_inverse = nfc_util_bytes2num(credential_block_start_ptr + 8, 8); + // Due to endianness, this is testing the bytes in the wrong order, + // but the result still should be correct. + if(cardholder_credential != ~cardholder_credential_inverse) { + return false; + } + + // Test 2: The contents of the second block should be equal to the GALLAGHER_CARDAX_ASCII constant. + const uint8_t* cardax_block_start_ptr = + &data->block[credential_sector_start_block_number + 1].data[0]; + if(memcmp(cardax_block_start_ptr, GALLAGHER_CARDAX_ASCII, MF_CLASSIC_BLOCK_SIZE) != 0) { + return false; + } + + // Deobfuscate the credential data + GallagherCredential credential; + gallagher_deobfuscate_and_parse_credential(&credential, credential_block_start_ptr); + + char display_region = 'A'; + // Per https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/cardholder.md, + // regions are generally A-P. + if(credential.region < 16) { + display_region = display_region + (char)credential.region; + } else { + display_region = '?'; + } + + furi_string_cat_printf( + parsed_data, + "\e#Gallagher NZ\nFacility %c%u\nCard %lu (IL %u)", + display_region, + credential.facility, + credential.card, + credential.issue); + return true; +} + +static const NfcSupportedCardsPlugin gallagher_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = gallagher_parse, +}; + +static const FlipperAppPluginDescriptor gallagher_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &gallagher_plugin, +}; + +/* Plugin entry point */ +const FlipperAppPluginDescriptor* gallagher_plugin_ep() { + return &gallagher_plugin_descriptor; +} \ No newline at end of file diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp index ef22ee9ad..11f2d7ecf 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.cpp +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -21,7 +21,7 @@ bool elf_resolve_from_hashtable( auto find_res = std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); if((find_res == hashtable_interface->table_cend || (find_res->hash != hash))) { - FURI_LOG_W( + FURI_LOG_T( TAG, "Can't find symbol with hash %lx @ %p!", hash, hashtable_interface->table_cbegin); result = false; } else { From 6bc63b7734a47887fdf9e7be8841cac460fc7f28 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 9 Feb 2024 08:07:54 +0000 Subject: [PATCH 19/36] [FL-3676] Slix disable privacy (#3425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * slix: add unlock option * slix: add features for nxp get info and signature commands * slix: working unlock * nfc app: rewrite slix unlock * slix poller: simplify unlock state handler * nfc app: fix slix key setting * nfc app: fix navigation * slix poller: code clean up * slix: resolve TODO, clean code * nfc app: fix naming * nfc app: rework slix unlock success scene * slix poller: add documentation * slix listener: fix password comparison Co-authored-by: あく --- applications/main/nfc/helpers/slix_unlock.c | 64 +++++++++++++++ applications/main/nfc/helpers/slix_unlock.h | 30 +++++++ applications/main/nfc/nfc_app.c | 2 + applications/main/nfc/nfc_app_i.h | 2 + .../main/nfc/scenes/nfc_scene_config.h | 5 ++ .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 +++ .../main/nfc/scenes/nfc_scene_retry_confirm.c | 6 +- .../nfc/scenes/nfc_scene_slix_key_input.c | 48 +++++++++++ .../main/nfc/scenes/nfc_scene_slix_unlock.c | 70 ++++++++++++++++ .../nfc/scenes/nfc_scene_slix_unlock_menu.c | 60 ++++++++++++++ .../scenes/nfc_scene_slix_unlock_success.c | 71 ++++++++++++++++ .../iso15693_3/iso15693_3_poller_i.c | 33 +------- lib/nfc/protocols/slix/slix.c | 2 +- lib/nfc/protocols/slix/slix.h | 4 + lib/nfc/protocols/slix/slix_i.c | 27 +++++++ lib/nfc/protocols/slix/slix_i.h | 4 +- lib/nfc/protocols/slix/slix_listener.c | 2 +- lib/nfc/protocols/slix/slix_listener_i.c | 2 +- lib/nfc/protocols/slix/slix_poller.c | 81 +++++++++++++++---- lib/nfc/protocols/slix/slix_poller.h | 34 ++++++++ lib/nfc/protocols/slix/slix_poller_i.c | 74 +++++++++++++++-- lib/nfc/protocols/slix/slix_poller_i.h | 5 ++ 22 files changed, 576 insertions(+), 60 deletions(-) create mode 100644 applications/main/nfc/helpers/slix_unlock.c create mode 100644 applications/main/nfc/helpers/slix_unlock.h create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c diff --git a/applications/main/nfc/helpers/slix_unlock.c b/applications/main/nfc/helpers/slix_unlock.c new file mode 100644 index 000000000..931ec1790 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.c @@ -0,0 +1,64 @@ +#include "slix_unlock.h" + +#include + +#define SLIX_UNLOCK_PASSWORD_NUM_MAX (2) + +struct SlixUnlock { + SlixUnlockMethod method; + SlixPassword password_arr[SLIX_UNLOCK_PASSWORD_NUM_MAX]; + size_t password_arr_len; + size_t password_idx; +}; + +static const SlixPassword tonie_box_pass_arr[] = {0x5B6EFD7F, 0x0F0F0F0F}; + +SlixUnlock* slix_unlock_alloc() { + SlixUnlock* instance = malloc(sizeof(SlixUnlock)); + + return instance; +} + +void slix_unlock_free(SlixUnlock* instance) { + furi_assert(instance); + + free(instance); +} + +void slix_unlock_reset(SlixUnlock* instance) { + furi_assert(instance); + + memset(instance, 0, sizeof(SlixUnlock)); +} + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method) { + furi_assert(instance); + + instance->method = method; + if(method == SlixUnlockMethodTonieBox) { + instance->password_arr_len = COUNT_OF(tonie_box_pass_arr); + memcpy(instance->password_arr, tonie_box_pass_arr, sizeof(tonie_box_pass_arr)); + } +} + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password) { + furi_assert(instance); + furi_assert(instance->method == SlixUnlockMethodManual); + + instance->password_arr[0] = password; + instance->password_arr_len = 1; +} + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password) { + furi_assert(instance); + furi_assert(password); + + bool password_set = false; + if(instance->password_arr_len) { + *password = instance->password_arr[instance->password_idx++]; + instance->password_idx %= instance->password_arr_len; + password_set = true; + } + + return password_set; +} diff --git a/applications/main/nfc/helpers/slix_unlock.h b/applications/main/nfc/helpers/slix_unlock.h new file mode 100644 index 000000000..c378891e4 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SlixUnlockMethodManual, + SlixUnlockMethodTonieBox, +} SlixUnlockMethod; + +typedef struct SlixUnlock SlixUnlock; + +SlixUnlock* slix_unlock_alloc(); + +void slix_unlock_free(SlixUnlock* instance); + +void slix_unlock_reset(SlixUnlock* instance); + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method); + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password); + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index c0ca2a917..8120268e0 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -51,6 +51,7 @@ NfcApp* nfc_app_alloc() { instance->nfc = nfc_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); + instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); instance->nfc_supported_cards = nfc_supported_cards_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); mf_ultralight_auth_free(instance->mf_ul_auth); + slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); nfc_supported_cards_free(instance->nfc_supported_cards); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 324ed6a5d..706815e62 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/slix_unlock.h" #include #include @@ -129,6 +130,7 @@ struct NfcApp { NfcListener* listener; MfUltralightAuth* mf_ul_auth; + SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 70e7c3d46..035c4949c 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -59,4 +59,9 @@ ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) ADD_SCENE(nfc, set_uid, SetUid) +ADD_SCENE(nfc, slix_unlock_menu, SlixUnlockMenu) +ADD_SCENE(nfc, slix_key_input, SlixKeyInput) +ADD_SCENE(nfc, slix_unlock, SlixUnlock) +ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) + ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index d14f80b62..2943c0c55 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexSlixUnlock, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +35,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexSlixUnlock, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_set_selected_item( submenu, scene_manager_get_scene_state(instance->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexReadCardType) { scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); consumed = true; + } else if(event.event == SubmenuIndexSlixUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockMenu); + consumed = true; } scene_manager_set_scene_state(instance->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 622996094..b6ad8144d 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -28,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSlixUnlock)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSlixUnlock); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfClassicDictAttack)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicDictAttack); } else if(scene_manager_has_previous_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_slix_key_input.c b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c new file mode 100644 index 000000000..1724eea8d --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_app_i.h" + +#include + +void nfc_scene_slix_key_input_byte_input_callback(void* context) { + NfcApp* instance = context; + + SlixPassword password = nfc_util_bytes2num(instance->byte_input_store, sizeof(SlixPassword)); + slix_unlock_set_password(instance->slix_unlock, password); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_slix_key_input_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the password in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_slix_key_input_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(SlixPassword)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_slix_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_slix_key_input_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c new file mode 100644 index 000000000..b01876e06 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c @@ -0,0 +1,70 @@ +#include "../nfc_app_i.h" + +#include + +NfcCommand nfc_scene_slix_unlock_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + SlixPollerEvent* slix_event = event.event_data; + if(slix_event->type == SlixPollerEventTypePrivacyUnlockRequest) { + SlixPassword pwd = 0; + bool get_password_success = slix_unlock_get_next_password(instance->slix_unlock, &pwd); + slix_event->data->privacy_password.password = pwd; + slix_event->data->privacy_password.password_set = get_password_success; + } else if(slix_event->type == SlixPollerEventTypeError) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + } else if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +void nfc_scene_slix_unlock_on_enter(void* context) { + NfcApp* instance = context; + + 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, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix); + nfc_poller_start(instance->poller, nfc_scene_slix_unlock_worker_callback, instance); +} + +bool nfc_scene_slix_unlock_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + UNUSED(instance); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventPollerFailure) { + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockSuccess); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSlixUnlockMenu); + } + + return consumed; +} + +void nfc_scene_slix_unlock_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c new file mode 100644 index 000000000..78eb9884e --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_app_i.h" + +enum SubmenuIndex { + SubmenuIndexSlixUnlockMenuManual, + SubmenuIndexSlixUnlockMenuTonieBox, +}; + +void nfc_scene_slix_unlock_menu_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_slix_unlock_menu_on_enter(void* context) { + NfcApp* instance = context; + Submenu* submenu = instance->submenu; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSlixUnlockMenu); + submenu_add_item( + submenu, + "Enter Password Manually", + SubmenuIndexSlixUnlockMenuManual, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_add_item( + submenu, + "Auth As TommyBox", + SubmenuIndexSlixUnlockMenuTonieBox, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_slix_unlock_menu_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSlixUnlockMenuManual) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodManual); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexSlixUnlockMenuTonieBox) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodTonieBox); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSlixUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_slix_unlock_menu_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c new file mode 100644 index 000000000..f25eabab2 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c @@ -0,0 +1,71 @@ +#include "../nfc_app_i.h" + +static void nfc_scene_slix_unlock_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_slix_unlock_success_on_enter(void* context) { + NfcApp* instance = context; + + Widget* widget = instance->widget; + widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "SLIX Unlocked!"); + + FuriString* temp_str = furi_string_alloc_set_str("UID:"); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", uid[i]); + } + furi_string_cat_printf(temp_str, "\nPrivacy Mode: Disabled"); + widget_add_string_multiline_element( + widget, 0, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_slix_unlock_success_widget_callback, + instance); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + nfc_scene_slix_unlock_success_widget_callback, + instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_slix_unlock_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +void nfc_scene_slix_unlock_success_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index ca6f5435e..949390305 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -32,17 +32,7 @@ static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) { } } -static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) { - furi_assert(instance); - - if(instance->state == Iso15693_3PollerStateIdle) { - return iso15693_3_poller_activate(instance, NULL); - } - - return Iso15693_3ErrorNone; -} - -static Iso15693_3Error iso15693_3_poller_frame_exchange( +Iso15693_3Error iso15693_3_poller_send_frame( Iso15693_3Poller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, @@ -156,7 +146,7 @@ Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -183,7 +173,7 @@ Iso15693_3Error Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -284,20 +274,3 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security( return ret; } - -Iso15693_3Error iso15693_3_poller_send_frame( - Iso15693_3Poller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt) { - Iso15693_3Error ret; - - do { - ret = iso15693_3_poller_prepare_trx(instance); - if(ret != Iso15693_3ErrorNone) break; - - ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); - } while(false); - - return ret; -} diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 12dc6750d..b575baa72 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -338,7 +338,7 @@ const Iso15693_3Data* slix_get_base_data(const SlixData* data) { } SlixType slix_get_type(const SlixData* data) { - SlixType type = SlixTypeCount; + SlixType type = SlixTypeUnknown; do { if(iso15693_3_get_manufacturer_id(data->iso15693_3_data) != SLIX_NXP_MANUFACTURER_CODE) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index f6c1453c5..26341072b 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -37,6 +37,7 @@ extern "C" { #define SLIX_TYPE_FEATURE_EAS (1U << 4) #define SLIX_TYPE_FEATURE_SIGNATURE (1U << 5) #define SLIX_TYPE_FEATURE_PROTECTION (1U << 6) +#define SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO (1U << 7) typedef uint32_t SlixTypeFeatures; @@ -56,7 +57,9 @@ typedef enum { SlixTypeSlixS, SlixTypeSlixL, SlixTypeSlix2, + SlixTypeCount, + SlixTypeUnknown, } SlixType; typedef enum { @@ -71,6 +74,7 @@ typedef enum { typedef uint32_t SlixPassword; typedef uint8_t SlixSignature[SLIX_SIGNATURE_SIZE]; typedef bool SlixPrivacy; +typedef uint16_t SlixRandomNumber; typedef struct { uint8_t pointer; diff --git a/lib/nfc/protocols/slix/slix_i.c b/lib/nfc/protocols/slix/slix_i.c index 97d66484c..26ac80be9 100644 --- a/lib/nfc/protocols/slix/slix_i.c +++ b/lib/nfc/protocols/slix/slix_i.c @@ -99,6 +99,33 @@ SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer return error; } +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf) { + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t random_number[2]; + } SlixGetRandomNumberResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixGetRandomNumberResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixGetRandomNumberResponseLayout* response = + (const SlixGetRandomNumberResponseLayout*)bit_buffer_get_data(buf); + *data = (response->random_number[1] << 8) | response->random_number[0]; + } while(false); + + return error; +} + void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password) { furi_assert(data); furi_assert(password_type < SlixPasswordTypeCount); diff --git a/lib/nfc/protocols/slix/slix_i.h b/lib/nfc/protocols/slix/slix_i.h index b5e445f31..4a15b50ff 100644 --- a/lib/nfc/protocols/slix/slix_i.h +++ b/lib/nfc/protocols/slix/slix_i.h @@ -48,7 +48,7 @@ extern "C" { #define SLIX_TYPE_FEATURES_SLIX2 \ (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS | SLIX_TYPE_FEATURE_SIGNATURE | \ - SLIX_TYPE_FEATURE_PROTECTION) + SLIX_TYPE_FEATURE_PROTECTION | SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO) #define SLIX2_FEATURE_FLAGS \ (SLIX_FEATURE_FLAG_UM_PP | SLIX_FEATURE_FLAG_COUNTER | SLIX_FEATURE_FLAG_EAS_ID | \ @@ -74,6 +74,8 @@ SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffe SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf); +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf); + // Setters void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password); diff --git a/lib/nfc/protocols/slix/slix_listener.c b/lib/nfc/protocols/slix/slix_listener.c index 204be5ab9..6ff390380 100644 --- a/lib/nfc/protocols/slix/slix_listener.c +++ b/lib/nfc/protocols/slix/slix_listener.c @@ -63,7 +63,7 @@ static NfcCommand slix_listener_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { const SlixError error = slix_listener_process_request(instance, rx_buffer); if(error == SlixErrorWrongPassword) { - command = NfcCommandStop; + command = NfcCommandSleep; } } diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index dfcb6c880..15ab2cd3c 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -31,7 +31,7 @@ static SlixPasswordType slix_listener_get_password_type_by_id(uint8_t id) { static SlixPassword slix_listener_unxor_password(const SlixPassword password_xored, uint16_t random) { - return password_xored ^ ((SlixPassword)random << 16 | random); + return REVERSE_BYTES_U32(password_xored ^ ((SlixPassword)random << 16 | random)); } static SlixError slix_listener_set_password( diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 46a171194..d9d38d102 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -44,33 +44,83 @@ static void slix_poller_free(SlixPoller* instance) { static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { iso15693_3_copy( instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); - - instance->poller_state = SlixPollerStateGetNxpSysInfo; + instance->type = slix_get_type(instance->data); + if(instance->type >= SlixTypeCount) { + instance->error = SlixErrorNotSupported; + instance->poller_state = SlixPollerStateError; + } else { + instance->poller_state = SlixPollerStateGetNxpSysInfo; + } return NfcCommandContinue; } static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { - instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReadSignature; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO)) { + instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReadSignature; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReadSignature; } return NfcCommandContinue; } static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { - instance->error = slix_poller_read_signature(instance, &instance->data->signature); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReady; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_SIGNATURE)) { + instance->error = slix_poller_read_signature(instance, &instance->data->signature); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReady; } return NfcCommandContinue; } +static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->poller_state = SlixPollerStateError; + + instance->slix_event.type = SlixPollerEventTypePrivacyUnlockRequest; + command = instance->callback(instance->general_event, instance->context); + + bool slix_unlocked = false; + do { + if(!instance->slix_event_data.privacy_password.password_set) break; + SlixPassword pwd = instance->slix_event_data.privacy_password.password; + FURI_LOG_I(TAG, "Trying to disable privacy mode with password: %08lX", pwd); + + instance->error = slix_poller_get_random_number(instance, &instance->random_number); + if(instance->error != SlixErrorNone) break; + + instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + if(instance->error != SlixErrorNone) { + command = NfcCommandReset; + break; + } + + FURI_LOG_I(TAG, "Privacy mode disabled"); + instance->data->passwords[SlixPasswordTypePrivacy] = pwd; + instance->poller_state = SlixPollerStateIdle; + slix_unlocked = true; + } while(false); + + if(!slix_unlocked) { + instance->error = SlixErrorTimeout; + instance->poller_state = SlixPollerStateError; + furi_delay_ms(100); + } + + return command; +} + static NfcCommand slix_poller_handler_error(SlixPoller* instance) { instance->slix_event_data.error = instance->error; instance->slix_event.type = SlixPollerEventTypeError; @@ -90,6 +140,7 @@ static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum [SlixPollerStateError] = slix_poller_handler_error, [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStatePrivacyUnlock] = slix_poller_handler_privacy_unlock, [SlixPollerStateReady] = slix_poller_handler_ready, }; @@ -117,8 +168,8 @@ static NfcCommand slix_poller_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { command = slix_poller_state_handler[instance->poller_state](instance); } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { - instance->slix_event.type = SlixPollerEventTypeError; - command = instance->callback(instance->general_event, instance->context); + instance->poller_state = SlixPollerStatePrivacyUnlock; + command = slix_poller_state_handler[instance->poller_state](instance); } return command; @@ -138,11 +189,7 @@ static bool slix_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { - if(slix_get_type(instance->data) < SlixTypeCount) { - SlixSystemInfo system_info = {}; - SlixError error = slix_poller_get_nxp_system_info(instance, &system_info); - protocol_detected = (error == SlixErrorNone); - } + protocol_detected = (slix_get_type(instance->data) < SlixTypeCount); } return protocol_detected; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 62d60be5f..4ea7f880d 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -18,14 +18,24 @@ typedef struct SlixPoller SlixPoller; */ typedef enum { SlixPollerEventTypeError, /**< An error occured while reading card. */ + SlixPollerEventTypePrivacyUnlockRequest, /**< Poller requests password to disable privacy mode. */ SlixPollerEventTypeReady, /**< The card was successfully read by the poller. */ } SlixPollerEventType; +/** + * @brief Slix poller privacy unlock context data. + */ +typedef struct { + SlixPassword password; /**< Privacy password. */ + bool password_set; /**< Filed to indicate that password was set or not. */ +} SlixPollerEventDataPrivacyUnlockContext; + /** * @brief Slixs poller event data. */ typedef union { SlixError error; /**< Error code indicating card reaing fail reason. */ + SlixPollerEventDataPrivacyUnlockContext privacy_password; /**< Privacy unlock event context. */ } SlixPollerEventData; /** @@ -80,6 +90,30 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* */ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data); +/** + * @brief Get random number from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixRandomNumber structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data); + +/** + * @brief Set password to card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] type SlixPasswordType instance. + * @param[out] password SlixPassword instance. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 6d7bdf377..5000efceb 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -1,4 +1,5 @@ #include "slix_poller_i.h" +#include #include @@ -6,18 +7,22 @@ #define TAG "SlixPoller" -static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command) { +static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command, bool skip_uid) { bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); - bit_buffer_append_byte( - instance->tx_buffer, - ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | - ISO15693_3_REQ_FLAG_T4_ADDRESSED); + uint8_t flags = ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI; + if(!skip_uid) { + flags |= ISO15693_3_REQ_FLAG_T4_ADDRESSED; + } + + bit_buffer_append_byte(instance->tx_buffer, flags); bit_buffer_append_byte(instance->tx_buffer, command); bit_buffer_append_byte(instance->tx_buffer, SLIX_NXP_MANUFACTURER_CODE); - iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + if(!skip_uid) { + iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + } } SlixError slix_poller_send_frame( @@ -36,7 +41,7 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION); + slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION, false); SlixError error = SlixErrorNone; @@ -54,7 +59,7 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE); + slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE, false); SlixError error = SlixErrorNone; @@ -67,3 +72,56 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) return error; } + +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_GET_RANDOM_NUMBER, true); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(error != SlixErrorNone) break; + + error = slix_get_random_number_response_parse(data, instance->rx_buffer); + } while(false); + + return error; +} + +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { + furi_assert(instance); + + bool skip_uid = (type == SlixPasswordTypePrivacy); + slix_poller_prepare_request(instance, SLIX_CMD_SET_PASSWORD, skip_uid); + + uint8_t password_type = (0x01 << type); + bit_buffer_append_byte(instance->tx_buffer, password_type); + + uint8_t rn_l = instance->random_number >> 8; + uint8_t rn_h = instance->random_number; + uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l; + uint32_t xored_password = double_rand_num ^ password; + uint8_t xored_password_arr[4] = {}; + nfc_util_num2bytes(xored_password, 4, xored_password_arr); + bit_buffer_append_bytes(instance->tx_buffer, xored_password_arr, 4); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, SLIX_POLLER_SET_PASSWORD_FWT); + if(error != SlixErrorNone) break; + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len != 1) { + error = SlixErrorWrongPassword; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index 1fda1a7d2..7a3b543b7 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -4,6 +4,8 @@ #include "slix_poller.h" +#define SLIX_POLLER_SET_PASSWORD_FWT (100000) + #ifdef __cplusplus extern "C" { #endif @@ -12,6 +14,7 @@ typedef enum { SlixPollerStateIdle, SlixPollerStateGetNxpSysInfo, SlixPollerStateReadSignature, + SlixPollerStatePrivacyUnlock, SlixPollerStateReady, SlixPollerStateError, SlixPollerStateNum, @@ -19,9 +22,11 @@ typedef enum { struct SlixPoller { Iso15693_3Poller* iso15693_3_poller; + SlixType type; SlixData* data; SlixPollerState poller_state; SlixError error; + SlixRandomNumber random_number; BitBuffer* tx_buffer; BitBuffer* rx_buffer; From ebd09a198133bbfd649e066b48e64ff006236caa Mon Sep 17 00:00:00 2001 From: Alessandro Rossi Date: Fri, 9 Feb 2024 09:16:14 +0100 Subject: [PATCH 20/36] Added NFC plugin; Some parser (#3335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add parser * Fix microel.c * Fix NFC parser positive return * fix mizip * Fix NFC parser positive return * Add parse to hi! tag * fix false positive reading and kdf * hi formatting * fix oom in kdf * nfc app: fix types in hi plugin * nfc app: fix return in function body in microel plugin Co-authored-by: gornekich Co-authored-by: あく --- applications/main/nfc/application.fam | 27 ++ .../main/nfc/plugins/supported_cards/hi.c | 226 +++++++++++++++ .../nfc/plugins/supported_cards/microel.c | 228 ++++++++++++++++ .../main/nfc/plugins/supported_cards/mizip.c | 257 ++++++++++++++++++ 4 files changed, 738 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hi.c create mode 100644 applications/main/nfc/plugins/supported_cards/microel.c create mode 100644 applications/main/nfc/plugins/supported_cards/mizip.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 1e6291c4d..4bcc79823 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -29,6 +29,33 @@ App( sources=["plugins/supported_cards/all_in_one.c"], ) +App( + appid="microel_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="microel_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/microel.c"], +) + +App( + appid="mizip_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="mizip_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/mizip.c"], +) + +App( + appid="hi_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hi_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hi.c"], +) + App( appid="opal_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c new file mode 100644 index 000000000..21e602877 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -0,0 +1,226 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "HI!" +#define KEY_LENGTH 6 +#define HI_KEY_TO_GEN 5 +#define UID_LENGTH 7 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} HiCardConfig; + +static MfClassicKeyPair hi_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x30871CF60CF1}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x42FFE4C76209, .b = 0x7B30CFD04CBD}, // 005 + {.a = 0x01ED8145BDF8, .b = 0x92257F472FCE}, // 006 + {.a = 0x7583A07D21A6, .b = 0x51CA6EA8EE26}, // 007 + {.a = 0x1E10BF5D6A1D, .b = 0x87B9B9BFABA6}, // 008 + {.a = 0xF9DB1B2B21BA, .b = 0x80A781F4134C}, // 009 + {.a = 0x7F5283FACB72, .b = 0x73250009D75A}, // 010 + {.a = 0xE48E86A03078, .b = 0xCFFBBF08A254}, // 011 + {.a = 0x39AB26301F60, .b = 0xC71A6E532C83}, // 012 + {.a = 0xAD656C6C639F, .b = 0xFD9819CBD20A}, // 013 + {.a = 0xF0E15160DB3E, .b = 0x3F622D515ADD}, // 014 + {.a = 0x03F44E033C42, .b = 0x61E897875F46}, // 015 +}; + +//KDF +void hi_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyB[4][6] = { + {0x1F, 0xC4, 0x4D, 0x94, 0x6A, 0x31}, + {0x12, 0xC1, 0x5C, 0x70, 0xDF, 0x31}, + {0x56, 0xF0, 0x13, 0x1B, 0x63, 0xF2}, + {0x4E, 0xFA, 0xC2, 0xF8, 0xC9, 0xCC}}; + + static const uint8_t xor_table_keyA[4][6] = { + {0xB6, 0xE6, 0xAE, 0x72, 0x91, 0x0D}, + {0x6D, 0x38, 0x50, 0xFB, 0x42, 0x89}, + {0x1E, 0x5F, 0xC7, 0xED, 0xAA, 0x02}, + {0x7E, 0xB9, 0xCA, 0xF1, 0x9C, 0x59}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 2}; + static const uint8_t xorOrderB[6] = {1, 3, 3, 2, 1, 0}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool hi_get_card_config(HiCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = hi_1k_keys; + } else { + success = false; + } + + return success; +} + +static bool hi_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a HI card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a HI Card"); + verified = true; + } while(false); + + return verified; +} + +static bool hi_verify(Nfc* nfc) { + return hi_verify_type(nfc, MfClassicType1k); +} + +static bool hi_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering HI KDF"); + 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 = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[HI_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[HI_KEY_TO_GEN][KEY_LENGTH]; + hi_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.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 != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool hi_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //parse data + furi_string_cat_printf(parsed_data, "\e#HI! Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf(parsed_data, "\n"); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hi_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hi_verify, + .read = hi_read, + .parse = hi_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hi_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hi_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hi_plugin_ep() { + return &hi_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/microel.c b/applications/main/nfc/plugins/supported_cards/microel.c new file mode 100644 index 000000000..899ad2a7e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/microel.c @@ -0,0 +1,228 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "Microel" +#define KEY_LENGTH 6 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair microel_1k_keys[] = { + {.a = 0x000000000000, .b = 0x000000000000}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 002 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 003 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 004 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 005 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 006 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 007 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 008 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 009 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 010 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 011 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 012 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 013 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 014 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 015 +}; + +const uint8_t verify_sector = 1; + +void calculateSumHex(const uint8_t* uid, size_t uidSize, uint8_t sumHex[]) { + const uint8_t xorKey[] = {0x01, 0x92, 0xA7, 0x75, 0x2B, 0xF9}; + int sum = 0; + + for(size_t i = 0; i < uidSize; i++) { + sum += uid[i]; + } + + int sumTwoDigits = sum % 256; + + if(sumTwoDigits % 2 == 1) { + sumTwoDigits += 2; + } + + for(size_t i = 0; i < sizeof(xorKey); i++) { + sumHex[i] = sumTwoDigits ^ xorKey[i]; + } +} + +void generateKeyA(const uint8_t* uid, uint8_t uidSize, uint8_t keyA[]) { + uint8_t sumHex[6]; + calculateSumHex(uid, uidSize, sumHex); + uint8_t firstCharacter = (sumHex[0] >> 4) & 0xF; + + if(firstCharacter == 0x2 || firstCharacter == 0x3 || firstCharacter == 0xA || + firstCharacter == 0xB) { + // XOR WITH 0x40 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0x40 ^ sumHex[i]; + } + } else if( + firstCharacter == 0x6 || firstCharacter == 0x7 || firstCharacter == 0xE || + firstCharacter == 0xF) { + // XOR WITH 0xC0 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0xC0 ^ sumHex[i]; + } + } else { + //Key a is the same as sumHex + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = sumHex[i]; + } + } +} + +void generateKeyB(uint8_t keyA[], size_t keyASize, uint8_t keyB[]) { + for(size_t i = 0; i < keyASize; i++) { + keyB[i] = 0xFF ^ keyA[i]; + } +} + +static bool microel_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering Microel KDF"); + + 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 = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + //Get UID and check if it is 4 bytes + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + FURI_LOG_D(TAG, "UID identified: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); + if(uid_len != UID_LENGTH) break; + + // Generate keys + uint8_t keyA[KEY_LENGTH]; + uint8_t keyB[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + generateKeyB(keyA, KEY_LENGTH, keyB); + + // Check key 0a to verify if it is a microel card + MfClassicKey key = {0}; + nfc_util_num2bytes(nfc_util_bytes2num(keyA, KEY_LENGTH), COUNT_OF(key.data), key.data); + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(0); // This is 0 + MfClassicAuthContext auth_context; + error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + break; + } + + // Save keys generated to stucture + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(microel_1k_keys[i].a == 0x000000000000) { + microel_1k_keys[i].a = nfc_util_bytes2num(keyA, KEY_LENGTH); + } + if(microel_1k_keys[i].b == 0x000000000000) { + microel_1k_keys[i].b = nfc_util_bytes2num(keyB, KEY_LENGTH); + } + } + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(microel_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 != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool microel_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + //Get UID + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + if(uid_len != UID_LENGTH) break; + + // Generate key from uid + uint8_t keyA[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key_for_check_from_array = nfc_util_bytes2num(keyA, KEY_LENGTH); + if(key != key_for_check_from_array) break; + + //Get credit in block number 8 + const uint8_t* temp_ptr = data->block[4].data; + uint16_t balance = (temp_ptr[6] << 8) | (temp_ptr[5]); + uint16_t previus_balance = (data->block[5].data[6] << 8) | (data->block[5].data[5]); + furi_string_cat_printf(parsed_data, "\e#Microel Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin microel_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = + NULL, // the verification I need is based on verifying the keys generated via uid and try to authenticate not like on mizip that there is default b0 but added verify in read function + .read = microel_read, + .parse = microel_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor microel_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = µel_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* microel_plugin_ep() { + return µel_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c new file mode 100644 index 000000000..bbcf9fdbc --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -0,0 +1,257 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "MiZIP" +#define KEY_LENGTH 6 +#define MIZIP_KEY_TO_GEN 5 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} MizipCardConfig; + +static MfClassicKeyPair mizip_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x0222179AB995, .b = 0x13321774F9B5}, // 005 + {.a = 0xB25CBD76A7B4, .b = 0x7571359B4274}, // 006 + {.a = 0xDA857B4907CC, .b = 0xD26B856175F7}, // 007 + {.a = 0x16D85830C443, .b = 0x8F790871A21E}, // 008 + {.a = 0x88BD5098FC82, .b = 0xFCD0D77745E4}, // 009 + {.a = 0x983349449D78, .b = 0xEA2631FBDEDD}, // 010 + {.a = 0xC599F962F3D9, .b = 0x949B70C14845}, // 011 + {.a = 0x72E668846BE8, .b = 0x45490B5AD707}, // 012 + {.a = 0xBCA105E5685E, .b = 0x248DAF9D674D}, // 013 + {.a = 0x4F6FE072D1FD, .b = 0x4250A05575FA}, // 014 + {.a = 0x56438ABE8152, .b = 0x59A45912B311}, // 015 +}; + +static MfClassicKeyPair mizip_mini_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 +}; + +//KDF +void mizip_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyA[4][6] = { + {0x09, 0x12, 0x5A, 0x25, 0x89, 0xE5}, + {0xAB, 0x75, 0xC9, 0x37, 0x92, 0x2F}, + {0xE2, 0x72, 0x41, 0xAF, 0x2C, 0x09}, + {0x31, 0x7A, 0xB7, 0x2F, 0x44, 0x90}}; + + static const uint8_t xor_table_keyB[4][6] = { + {0xF1, 0x2C, 0x84, 0x53, 0xD8, 0x21}, + {0x73, 0xE7, 0x99, 0xFE, 0x32, 0x41}, + {0xAA, 0x4D, 0x13, 0x76, 0x56, 0xAE}, + {0xB0, 0x13, 0x27, 0x27, 0x2D, 0xFD}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 1}; + static const uint8_t xorOrderB[6] = {2, 3, 0, 1, 2, 3}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool mizip_get_card_config(MizipCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = mizip_1k_keys; + } else if(type == MfClassicTypeMini) { + config->verify_sector = 0; + config->keys = mizip_mini_keys; + } else { + success = false; + } + + return success; +} + +static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a MiZIP card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a MiZIP Card"); + verified = true; + } while(false); + + return verified; +} + +static bool mizip_verify(Nfc* nfc) { + return mizip_verify_type(nfc, MfClassicType1k) || mizip_verify_type(nfc, MfClassicTypeMini); +} + +static bool mizip_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering MiZIP KDF"); + 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; + + //temp fix but fix mf_classic_poller_sync_detect_type because view type mfclassic1k and not verify mfmini + data->type = MfClassicTypeMini; + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + mizip_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.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 != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //Get credit + uint8_t credit_pointer = 0x08; + uint8_t previus_credit_pointer = 0x09; + if(data->block[10].data[0] == 0x55) { + credit_pointer = 0x09; + previus_credit_pointer = 0x08; + } + uint16_t balance = (data->block[credit_pointer].data[2] << 8) | + (data->block[credit_pointer].data[1]); + uint16_t previus_balance = (data->block[previus_credit_pointer].data[2] << 8) | + (data->block[previus_credit_pointer].data[1]); + + //parse data + furi_string_cat_printf(parsed_data, "\e#MiZIP Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin mizip_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = mizip_verify, + .read = mizip_read, + .parse = mizip_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor mizip_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &mizip_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* mizip_plugin_ep() { + return &mizip_plugin_descriptor; +} From 50e0521bf7f2e37e98286520da51db9b6d4c1556 Mon Sep 17 00:00:00 2001 From: Tolly Hill Date: Fri, 9 Feb 2024 08:36:06 +0000 Subject: [PATCH 21/36] NFC: Custom UID entry when adding manually (#3363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Custom UID entry when adding manually * Fix incorrect types * Add Change UID option post-generation * Update UID derived data when using set_uid method * Fix PVS warnings Co-authored-by: gornekich Co-authored-by: あく --- .../protocol_support/mf_classic/mf_classic.c | 3 ++ .../mf_ultralight/mf_ultralight.c | 11 +++-- .../protocol_support/nfc_protocol_support.c | 9 ++++ .../main/nfc/plugins/supported_cards/hi.c | 2 +- .../main/nfc/plugins/supported_cards/mizip.c | 2 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 4 ++ lib/nfc/helpers/nfc_data_generator.c | 43 +++++-------------- lib/nfc/protocols/mf_classic/mf_classic.c | 20 ++++++++- .../protocols/mf_ultralight/mf_ultralight.c | 14 +++++- 9 files changed, 69 insertions(+), 39 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 148601191..6abaa48cd 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -199,6 +199,9 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag } else if(event.event == SubmenuIndexDictAttack) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } 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 f0a46dd87..eb6911df7 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 @@ -266,16 +266,21 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexUnlock) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); - return true; + consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - return true; + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } - return false; + return consumed; } const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { 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 2d46810a2..80fbb63de 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -233,6 +233,15 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneGenerateInfo)) { + submenu_add_item( + submenu, + "Change UID", + SubmenuIndexCommonEdit, + nfc_protocol_support_common_submenu_callback, + instance); + } + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c index 21e602877..6807ab00c 100644 --- a/applications/main/nfc/plugins/supported_cards/hi.c +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -88,7 +88,7 @@ static bool hi_verify_type(Nfc* nfc, MfClassicType type) { if(!hi_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index bbcf9fdbc..b76c381e9 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -99,7 +99,7 @@ static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { if(!mizip_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index df8a4dc72..f09d0a430 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -44,6 +44,10 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } + } else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadMenu)) { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneReadMenu); + consumed = true; } else { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 21f062605..0ecccc3ac 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -35,26 +35,16 @@ static void nfc_generate_mf_ul_uid(uint8_t* uid) { } static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) { + uint8_t uid[7]; mfu_data->iso14443_3a_data->uid_len = 7; - nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid); + nfc_generate_mf_ul_uid(uid); + mf_ultralight_set_uid(mfu_data, uid, 7); + mfu_data->iso14443_3a_data->atqa[0] = 0x44; mfu_data->iso14443_3a_data->atqa[1] = 0x00; mfu_data->iso14443_3a_data->sak = 0x00; } -static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { - *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; - *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; -} - -static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) { - memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3); - memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4); - - nfc_generate_calc_bcc( - mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]); -} - static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { MfUltralightData* mfu_data = mf_ultralight_alloc(); nfc_generate_mf_ul_common(mfu_data); @@ -62,7 +52,6 @@ static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeUnknown; mfu_data->pages_total = 16; mfu_data->pages_read = 16; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage)); nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); @@ -74,7 +63,7 @@ static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, ui mfu_data->pages_total = num_pages; mfu_data->pages_read = num_pages; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + uint16_t config_index = (num_pages - 4); mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN mfu_data->page[config_index].data[3] = 0xff; // AUTH0 @@ -150,7 +139,6 @@ static void nfc_generate_ntag203(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeNTAG203; mfu_data->pages_total = 42; mfu_data->pages_read = 42; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); mfu_data->page[2].data[1] = 0x48; // Internal byte memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086 @@ -379,14 +367,7 @@ static void nfc_generate_mf_classic_block_0( furi_assert(uid_len == 4 || uid_len == 7); furi_assert(block); - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(int i = 0; i < uid_len; i++) { - block[uid_len] ^= block[i]; - } - } else { + if(uid_len == 7) { uid_len -= 1; } @@ -402,14 +383,12 @@ static void nfc_generate_mf_classic_block_0( static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) { MfClassicData* mfc_data = mf_classic_alloc(); - nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len); - nfc_generate_mf_classic_common(mfc_data, uid_len, type); + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; - // Set the UID - mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID; - for(int i = 1; i < uid_len; i++) { - mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i]; - } + nfc_generate_mf_classic_uid(uid, uid_len); + mf_classic_set_uid(mfc_data, uid, uid_len); + + nfc_generate_mf_classic_common(mfc_data, uid_len, type); mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index e68e8c718..27236df4b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -346,7 +346,25 @@ const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) { bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + uint8_t* block = data->block[0].data; + + // Copy UID to block 0 + memcpy(block, data->iso14443_3a_data->uid, uid_len); + + if(uid_len == 4) { + // Calculate BCC byte + block[uid_len] = 0; + + for(size_t i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } + } + + return uid_valid; } Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index fd845f3c0..3cffa56ff 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -482,7 +482,19 @@ const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_l bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + // Copy UID across first 2 pages + memcpy(data->page[0].data, data->iso14443_3a_data->uid, 3); + memcpy(data->page[1].data, &data->iso14443_3a_data->uid[3], 4); + + // Calculate BCC bytes + data->page[0].data[3] = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + data->page[2].data[0] = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; + } + + return uid_valid; } Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) { From c0db3d541e6fe6ea202b909044c9fd3f5056831d Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:49:58 +0400 Subject: [PATCH 22/36] [FL-3753] UI SubGhz: fix UI only_rx scene (#3379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UI SubGhz: fix UI only_rx scene * UI SubGhz: delete unused scene Co-authored-by: あく --- .../main/subghz/scenes/subghz_scene_config.h | 1 - .../subghz/scenes/subghz_scene_show_only_rx.c | 49 ------------------- applications/main/subghz/subghz_i.c | 8 +-- 3 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 applications/main/subghz/scenes/subghz_scene_show_only_rx.c diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 47655958b..60bc8528a 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -8,7 +8,6 @@ ADD_SCENE(subghz, saved, Saved) ADD_SCENE(subghz, transmitter, Transmitter) ADD_SCENE(subghz, show_error, ShowError) ADD_SCENE(subghz, show_error_sub, ShowErrorSub) -ADD_SCENE(subghz, show_only_rx, ShowOnlyRx) ADD_SCENE(subghz, saved_menu, SavedMenu) ADD_SCENE(subghz, delete, Delete) ADD_SCENE(subghz, delete_success, DeleteSuccess) diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c deleted file mode 100644 index 3522bf8aa..000000000 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../subghz_i.h" -#include "../helpers/subghz_custom_event.h" - -void subghz_scene_show_only_rx_popup_callback(void* context) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneShowOnlyRX); -} - -void subghz_scene_show_only_rx_on_enter(void* context) { - SubGhz* subghz = context; - - // Setup view - Popup* popup = subghz->popup; - - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; - if(!furi_hal_region_is_provisioned()) { - header_text = "Firmware update needed"; - message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; - } - - popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); - popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); - popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); - - popup_set_timeout(popup, 1500); - popup_set_context(popup, subghz); - popup_set_callback(popup, subghz_scene_show_only_rx_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); -} - -bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzCustomEventSceneShowOnlyRX) { - scene_manager_previous_scene(subghz->scene_manager); - return true; - } - } - return false; -} - -void subghz_scene_show_only_rx_on_exit(void* context) { - SubGhz* subghz = context; - Popup* popup = subghz->popup; - - popup_reset(popup); -} diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 4358b164d..b553d00de 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -60,15 +60,15 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { DialogsApp* dialogs = subghz->dialogs; DialogMessage* message = dialog_message_alloc(); - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; + const char* header_text = "Transmission is Blocked!"; + const char* message_text = "Transmission on\nthis frequency is\nrestricted in your\nregion"; if(!furi_hal_region_is_provisioned()) { header_text = "Firmware update needed"; message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; } - dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); - dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); + dialog_message_set_header(message, header_text, 63, 0, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 1, 13, AlignLeft, AlignTop); dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); From b2a7bb0696316c94a75d1d5f3e21dcce2068014e Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 11:59:24 +0300 Subject: [PATCH 23/36] [FL-3672] CLI: cat command crash workaround (#3358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cli input len limit, CDC EP type fix Co-authored-by: あく --- applications/services/cli/cli.c | 6 ++- targets/f7/furi_hal/furi_hal_usb_cdc.c | 53 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 55a603a20..41e658df1 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -6,6 +6,8 @@ #define TAG "CliSrv" +#define CLI_INPUT_LEN_LIMIT 256 + Cli* cli_alloc() { Cli* cli = malloc(sizeof(Cli)); @@ -356,7 +358,9 @@ void cli_process_input(Cli* cli) { cli_handle_backspace(cli); } else if(in_chr == CliSymbolAsciiCR) { cli_handle_enter(cli); - } else if(in_chr >= 0x20 && in_chr < 0x7F) { //-V560 + } else if( + (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 + (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { if(cli->cursor_position == furi_string_size(cli->line)) { furi_string_push_back(cli->line, in_chr); cli_putc(cli, in_chr); diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index e9cb51e20..014c98bad 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -7,13 +7,13 @@ #include "usb.h" #include "usb_cdc.h" -#define CDC0_RXD_EP 0x01 +#define CDC0_RXD_EP 0x02 #define CDC0_TXD_EP 0x82 -#define CDC0_NTF_EP 0x83 +#define CDC0_NTF_EP 0x81 #define CDC1_RXD_EP 0x04 -#define CDC1_TXD_EP 0x85 -#define CDC1_NTF_EP 0x86 +#define CDC1_TXD_EP 0x84 +#define CDC1_NTF_EP 0x83 #define CDC_NTF_SZ 0x08 @@ -438,7 +438,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { struct usb_string_descriptor* dev_prod_desc = malloc(len * 2 + 2); dev_prod_desc->bLength = len * 2 + 2; dev_prod_desc->bDescriptorType = USB_DTYPE_STRING; - for(uint8_t i = 0; i < len; i++) dev_prod_desc->wString[i] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_prod_desc->wString[i] = name[i]; + } name = (char*)furi_hal_version_get_name_ptr(); len = (name == NULL) ? (0) : (strlen(name)); @@ -446,7 +448,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { dev_serial_desc->bLength = (len + 5) * 2 + 2; dev_serial_desc->bDescriptorType = USB_DTYPE_STRING; memcpy(dev_serial_desc->wString, "f\0l\0i\0p\0_\0", 5 * 2); - for(uint8_t i = 0; i < len; i++) dev_serial_desc->wString[i + 5] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_serial_desc->wString[i + 5] = name[i]; + } cdc_if_cur->str_prod_descr = dev_prod_desc; cdc_if_cur->str_serial_descr = dev_serial_desc; @@ -500,18 +504,20 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { } void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { - if(if_num == 0) + if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - else + } else { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } } int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; - if(if_num == 0) + if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - else + } else { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } return ((len < 0) ? 0 : len); } @@ -540,14 +546,16 @@ static void cdc_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_RXD_EP) + if(ep == CDC0_RXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->rx_ep_callback != NULL) + if(callbacks[if_num]->rx_ep_callback != NULL) { callbacks[if_num]->rx_ep_callback(cb_ctx[if_num]); + } } } @@ -555,14 +563,16 @@ static void cdc_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_TXD_EP) + if(ep == CDC0_TXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->tx_ep_callback != NULL) + if(callbacks[if_num]->tx_ep_callback != NULL) { callbacks[if_num]->tx_ep_callback(cb_ctx[if_num]); + } } } @@ -642,25 +652,28 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == (USB_REQ_INTERFACE | USB_REQ_CLASS) && (req->wIndex == 0 || req->wIndex == 2)) { - if(req->wIndex == 0) + if(req->wIndex == 0) { if_num = 0; - else + } else { if_num = 1; + } switch(req->bRequest) { case USB_CDC_SET_CONTROL_LINE_STATE: if(callbacks[if_num] != NULL) { cdc_ctrl_line_state[if_num] = req->wValue; - if(callbacks[if_num]->ctrl_line_callback != NULL) + if(callbacks[if_num]->ctrl_line_callback != NULL) { callbacks[if_num]->ctrl_line_callback( cb_ctx[if_num], cdc_ctrl_line_state[if_num]); + } } return usbd_ack; case USB_CDC_SET_LINE_CODING: memcpy(&cdc_config[if_num], req->data, sizeof(cdc_config[0])); if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->config_callback != NULL) + if(callbacks[if_num]->config_callback != NULL) { callbacks[if_num]->config_callback(cb_ctx[if_num], &cdc_config[if_num]); + } } return usbd_ack; case USB_CDC_GET_LINE_CODING: From 3c73123a81a7aeb1032680a2f33496c1396bb2b9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 12:33:47 +0300 Subject: [PATCH 24/36] HID app: keyboard modifiers fix (#3378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HID App: Modifier keys fix * Toggled keys indication * New enter and backspace button icons Co-authored-by: あく --- .../hid_app/assets/Alt_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Backspace_9x7.png | Bin 0 -> 6628 bytes .../hid_app/assets/Cmd_pressed_17x10.png | Bin 0 -> 4234 bytes .../hid_app/assets/Ctrl_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Return_10x7.png | Bin 0 -> 6630 bytes .../hid_app/assets/Shift_pressed_7x10.png | Bin 0 -> 6626 bytes .../system/hid_app/views/hid_keyboard.c | 212 ++++++++++-------- 7 files changed, 115 insertions(+), 97 deletions(-) create mode 100644 applications/system/hid_app/assets/Alt_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Backspace_9x7.png create mode 100644 applications/system/hid_app/assets/Cmd_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Ctrl_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Return_10x7.png create mode 100644 applications/system/hid_app/assets/Shift_pressed_7x10.png diff --git a/applications/system/hid_app/assets/Alt_pressed_17x10.png b/applications/system/hid_app/assets/Alt_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..e7421c8c0bf7d189200b2b953219f24b287f093d GIT binary patch literal 4227 zcmeHKYitx%6rSawWdYGr&_JS_sX(R7&dl!4?9QZHEZsg<9VoCswK4JT%-wcmU(QUs z+m@h$ii!~mY7_ybJbVI*f%pOy9~GlPFvK6cOf%D;wK(7YC*nbWCrscG>BKq{k_D??BJotWF9o|^bwv3t^{`l<5ldrUW`Od1=(%)N83kO@BZx^Z+ z+!w94e)ViidQ#`ARnPxEyY$GKr^jD7^~A(S_!4eZ`?~Fik8U}(_3)-y7Y=-I;?Bp% z+TVWlp+yCf_u-C*%ZoQe&ODP`yXf;6#QY1L&x{)P zTkDQ96L#IUiF~W_##MOPI_6Y9H(}qg{E_V)c-^l3moD$0Q@H2w$4#$AhECl*|Ge<-!p~oww{P5!KTcV+a&z6q0ecoUjQ+;vc0r1z)ljWb zE8QW=iI@{9i7(Oijz#0TnD&zvt_ZTJrb&loV$AiEwU9F92@N|uPLGqJ zD$;6`i!8M}N;D-R&InXyAz1!k!R{obyjMYEf%DicI4uj)AETtFpb8G%r`q z!=#{IjU_EFAiI~Pp+>sN>J^(gV{NBrAmEx)W4 zRu$0_ImI18OxQZjt>jsL;*n|RbrwTYcQuv4$ z@f4$k70Tm94CP}LFNNH^hvRr3gWcgC5YsdjvJypmMrA@N02TJ|ir`Z?N=B^DgkmYg zARmQUA>#HSA*^^<3rdm2%7hj}aGYuk)nhstueT;l!o{+hpr3R(nQl!@6d4h4@RN6_ z@y2xbL`aR{nFcc1WW5aMcC(zD=Y3wE&(&=-8*4gbqKRphNo&SD77;cB#3D0K0l?D3 zUc_<@BO{@O5{amv%oW(&v;@~lK?VvS0|RKT6%oaXt^vUV*MBB1Q^nxU3#{1g;=Gtsf&t zk#$hF^o}LffX!0?!YGENSnf)~=N|CU7&_8yHe$uf8$fxr5tJ&f?zD{pQotnI&C+@Y>R0&g5F|NZwt4bO0C;te6_uH07QJe*x!(u95%% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Backspace_9x7.png b/applications/system/hid_app/assets/Backspace_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..e098cf676167cdd78687b61cc1b61caf6e6b5666 GIT binary patch literal 6628 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_pressed_17x10.png b/applications/system/hid_app/assets/Cmd_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..274c3e070ae42bccbb0b463aa3396a12486704f6 GIT binary patch literal 4234 zcmeHKd2AF_7@y^kvRttTF*Lz(Do`o2bL`IU&ZG;L-O{eIP@qDwiE(!3Z9A}+JJasA zMMRK8G;#^bQPF~;7!DyAP!7QZF`9COK*G^*$Pr0U&WI5FW_G)@N@AMGf8EL3_r33Z z-|v0z_rCY-&YNXpO8S@wm@y3N<1O`!hyHPB=8P`zeQF?a6S^&^!W-~!D?WtlQ{qfT_FD&j2|$CC>vnKV_r)Fs3e8ALtiCV?oY_ z{sUL^aeeb3D5W(w;+g+xP zFByq@i`{mHx7$f@!?47vnnle8o^jdxM_A|e9+%%sIlNgsU*ZeCyQpl5ZPcIJme*aF zyTWn3{;F?9aOsAo?|#_S{WVi1+K|0yF*()$?Tu9zKU?(kj>d-Jw;QhVry4BV^OR!l zr-o;LdA~j}>{4Un%G(o%pZRF%;5(P!9y-Tf$mKMx-PLk-%eieW8(+9{a?gdy^K;GH z*S|40+vk|Qf3~~elfd<5(KU+YW1Ki%sqv|7n8d^@|(qNQ$suzU0Kx?MtL7qIDfE-lL$bgg0c z^&tnI+emC3HLwvaUQ1uj!D#oRy~Z?8zr}ww>&Nxejt;tVW#rsdn=0>iIW()f{}Gdwg%nFDmCIkQoAx*>+pi6mnwTKNYMu^} z#PA?hiYZY|iG*<-Q}jn_G#5d@Jl+~#DC+a2;lpZ*1>l29h*65M&{QZywe?W7;#vSn z1@ur4wY)BhsPRaR)WjrIT#Le5URwxRO8ZA^VnJg&vP7XE3W2E#t1|7D)Gt@sL#Loh z2}KPrAiJHVrUV|4)h;%D#@J5VK)^kX+s?Xm?}jm0`Fw&WBGu^Oc|9&dUtf?Tk|GO+ z$S_VMvl2t{D8P_5&dQRaHQ*%eh-XBZ;bkPTZJ@kiRTIM!(xCv{q5zJAlMpN0{ba!E zL;%4e(k})?QetVw$@w{lO}4dx7#mX{E5%^jsB|b9plF*_lKe7D(z4_yZM@S@`kg#S zav~BD?{sji-)=z3k}xU~3yE-?N=U3iR5V;=Oz4CQMP*(W!CL4CnzEp%1;D{Yj8?)m zi3bzqN(fERM4e5>L336s!`ZEjgJ+z{m<_y%D5gRt>X=5EG~D%L5nwYwtf=QH02q4M zi{OqSQH#XNBaxtsNEg`jv=6S6ENY@h)DVECOKy2<$t|F*0-X$Bhx0{bB~bTY*81ha z3&9?ayi`$P{W?RHyipU-E6GuE7*vd_gyY7g5JV}t1y!s?vJod>C8wlnF0uV;h49Rf+B#cUJn9^%T>y8Vle{oW17_=!e;Fr`v@q$`NrHf&T zGl=(5eo|-gD3`$T&P6)JZzo-ybajY<4jFf5S0`N^VxU9Do!Rxj(Pe({I)%dUFDMQ# zOF3ld3cPAP=6}A#gWcDk4|lCw0-B!D(#a}@^~%zl30tx12~hUXyuRWdmrVWo^)9Gb z`qyz#m3loz<@wEx3-+2ZJi}di#{bR4m1}XD=la~3z(RV!Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Ctrl_pressed_17x10.png b/applications/system/hid_app/assets/Ctrl_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..978a1090c2ee9f38e07c66adc5f4a3250885a048 GIT binary patch literal 4227 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 W(tV}+OFw`b5Nq>|)W6JpWB(JHjv}J~ literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 9060c1d6a..1ee2c01c9 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -20,19 +20,18 @@ typedef struct { uint8_t x; uint8_t y; uint8_t last_key_code; - uint16_t modifier_code; bool ok_pressed; bool back_pressed; bool connected; - char key_string[5]; HidTransport transport; } HidKeyboardModel; typedef struct { uint8_t width; - char* key; + char key; + char shift_key; const Icon* icon; - char* shift_key; + const Icon* icon_toggled; uint8_t value; } HidKeyboardKey; @@ -66,85 +65,97 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, }, { - {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, - {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, - {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, - {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, - {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, - {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, - {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, - {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, - {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, - {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, - {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 1, .icon = NULL, .key = '1', .shift_key = '!', .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = '2', .shift_key = '@', .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = '3', .shift_key = '#', .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = '4', .shift_key = '$', .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = '5', .shift_key = '%', .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = '6', .shift_key = '^', .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = '7', .shift_key = '&', .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = '8', .shift_key = '*', .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = '9', .shift_key = '(', .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = '0', .shift_key = ')', .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Backspace_9x7, .value = HID_KEYBOARD_DELETE}, {.width = 0, .value = HID_KEYBOARD_DELETE}, }, { - {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, - {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, - {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, - {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, - {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, - {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, - {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, - {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, - {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, - {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, - {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, .icon = NULL, .key = 'q', .shift_key = 'Q', .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = 'w', .shift_key = 'W', .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = 'e', .shift_key = 'E', .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = 'r', .shift_key = 'R', .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = 't', .shift_key = 'T', .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = 'y', .shift_key = 'Y', .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = 'u', .shift_key = 'U', .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = 'i', .shift_key = 'I', .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = 'o', .shift_key = 'O', .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = 'p', .shift_key = 'P', .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = '[', .shift_key = '{', .value = HID_KEYBOARD_OPEN_BRACKET}, {.width = 1, .icon = NULL, - .key = "]", - .shift_key = "}", + .key = ']', + .shift_key = '}', .value = HID_KEYBOARD_CLOSE_BRACKET}, }, { - {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, - {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, - {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, - {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, - {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, - {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, - {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, - {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, - {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, - {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, - {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 1, .icon = NULL, .key = 'a', .shift_key = 'A', .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = 's', .shift_key = 'S', .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = 'd', .shift_key = 'D', .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = 'f', .shift_key = 'F', .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = 'g', .shift_key = 'G', .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = 'h', .shift_key = 'H', .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = 'j', .shift_key = 'J', .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = 'k', .shift_key = 'K', .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = 'l', .shift_key = 'L', .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ';', .shift_key = ':', .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Return_10x7, .value = HID_KEYBOARD_RETURN}, {.width = 0, .value = HID_KEYBOARD_RETURN}, }, { - {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, - {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, - {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, - {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, - {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, - {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, - {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, - {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, - {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, - {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = NULL, .key = 'z', .shift_key = 'Z', .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = 'x', .shift_key = 'X', .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = 'c', .shift_key = 'C', .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = 'v', .shift_key = 'V', .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = 'b', .shift_key = 'B', .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = 'n', .shift_key = 'N', .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = 'm', .shift_key = 'M', .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = '/', .shift_key = '?', .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = '\\', .shift_key = '|', .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = '`', .shift_key = '~', .value = HID_KEYBOARD_GRAVE_ACCENT}, {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, - {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + {.width = 1, .icon = NULL, .key = '-', .shift_key = '_', .value = HID_KEYBOARD_MINUS}, }, { - {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, - {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, - {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, - {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, + .icon = &I_Pin_arrow_up_7x9, + .icon_toggled = &I_Shift_pressed_7x10, + .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ',', .shift_key = '<', .value = HID_KEYBOARD_COMMA}, + {.width = 1, .icon = NULL, .key = '.', .shift_key = '>', .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = ' ', .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, - {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = NULL, .key = '\'', .shift_key = '\"', .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = '=', .shift_key = '+', .value = HID_KEYBOARD_EQUAL_SIGN}, {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, }, { - {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, + .icon = &I_Ctrl_17x10, + .icon_toggled = &I_Ctrl_pressed_17x10, + .value = HID_KEYBOARD_L_CTRL}, {.width = 0, .value = HID_KEYBOARD_L_CTRL}, - {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, + .icon = &I_Alt_17x10, + .icon_toggled = &I_Alt_pressed_17x10, + .value = HID_KEYBOARD_L_ALT}, {.width = 0, .value = HID_KEYBOARD_L_ALT}, - {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, + .icon = &I_Cmd_17x10, + .icon_toggled = &I_Cmd_pressed_17x10, + .value = HID_KEYBOARD_L_GUI}, {.width = 0, .value = HID_KEYBOARD_L_GUI}, {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, {.width = 0, .value = HID_KEYBOARD_TAB}, @@ -155,13 +166,6 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { }, }; -static void hid_keyboard_to_upper(char* str) { - while(*str) { - *str = toupper((unsigned char)*str); - str++; - } -} - static void hid_keyboard_draw_key( Canvas* canvas, HidKeyboardModel* model, @@ -192,28 +196,32 @@ static void hid_keyboard_draw_key( KEY_HEIGHT); } if(key.icon != NULL) { + const Icon* key_icon = key.icon; + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->shift && key.value == HID_KEYBOARD_L_SHIFT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + if(key.icon_toggled) { + key_icon = key.icon_toggled; + } + } // Draw the icon centered on the button canvas_draw_icon( canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, - key.icon); + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key_icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key_icon->height / 2, + key_icon); } else { + char key_str[2] = "\0\0"; // If shift is toggled use the shift key when available - strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); - // Upper case if ctrl or alt was toggled true - if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || - (model->alt && key.value == HID_KEYBOARD_L_ALT) || - (model->gui && key.value == HID_KEYBOARD_L_GUI)) { - hid_keyboard_to_upper(model->key_string); - } + key_str[0] = (model->shift && key.shift_key != 0) ? key.shift_key : key.key; canvas_draw_str_aligned( canvas, MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, AlignCenter, AlignCenter, - model->key_string); + key_str); } } @@ -244,6 +252,8 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { initY = model->y - 4; } + elements_scrollbar(canvas, initY, 3); + for(uint8_t y = initY; y < ROW_COUNT; y++) { const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; uint8_t x = 0; @@ -286,6 +296,14 @@ static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoin 0); // Skip zero width keys, pretend they are one key } +static void hid_keyboard_modifier_set(Hid* hid, uint16_t keycode, bool is_pressed) { + if(is_pressed) { + hid_hal_keyboard_press(hid, keycode); + } else { + hid_hal_keyboard_release(hid, keycode); + } +} + static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { with_view_model( hid_keyboard->view, @@ -300,35 +318,25 @@ static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { // Toggle the modifier key when clicked, and click the key if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { model->shift = !model->shift; - if(model->shift) - model->modifier_code |= KEY_MOD_LEFT_SHIFT; - else - model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_SHIFT, model->shift); } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { model->alt = !model->alt; - if(model->alt) - model->modifier_code |= KEY_MOD_LEFT_ALT; - else - model->modifier_code &= ~KEY_MOD_LEFT_ALT; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_ALT, model->alt); } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { model->ctrl = !model->ctrl; - if(model->ctrl) - model->modifier_code |= KEY_MOD_LEFT_CTRL; - else - model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_CTRL, model->ctrl); } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { model->gui = !model->gui; - if(model->gui) - model->modifier_code |= KEY_MOD_LEFT_GUI; - else - model->modifier_code &= ~KEY_MOD_LEFT_GUI; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_GUI, model->gui); + } else { + hid_hal_keyboard_press(hid_keyboard->hid, model->last_key_code); } - hid_hal_keyboard_press( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { // Release happens after short and long presses - hid_hal_keyboard_release( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + hid_hal_keyboard_release(hid_keyboard->hid, model->last_key_code); model->ok_pressed = false; } } else if(event->key == InputKeyBack) { @@ -364,6 +372,16 @@ static bool hid_keyboard_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeLong && event->key == InputKeyBack) { hid_hal_keyboard_release_all(hid_keyboard->hid); + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { + model->shift = false; + model->alt = false; + model->ctrl = false; + model->gui = false; + }, + true); } else { hid_keyboard_process(hid_keyboard, event); consumed = true; From 962d809ad7b2dde665ef431be90d07b1ad1be4dc Mon Sep 17 00:00:00 2001 From: tomellens <156447023+tomellens@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:39:35 +0000 Subject: [PATCH 25/36] Update tv.ir (#3399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index d10e14df2..8a646ae51 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,3 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 +# +# Sony KD-55AG8 Smart TV +# +name: Power +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +name: Vol_up +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Ch_next +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 11 00 00 00 From bcdb9cb13cbcea9d487e9f6f18d8f50c95ec3609 Mon Sep 17 00:00:00 2001 From: JuicyPigWalker <34217507+JuicyPigWalker@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:12:46 +0100 Subject: [PATCH 26/36] Update tv.ir (#3421) * Update tv.ir: Added tv "TCL 50P715X1" recorded remote signals. Hope everything is okay, this is my first GitHub contribution. * Update tv.ir: Fixed my own mistakes when uploading the signals. * Infrared: revert 962d809ad7b2dde665ef431be90d07b1ad1be4dc Co-authored-by: Yoel <34217507+yoelci@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .../infrared/resources/infrared/assets/tv.ir | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 8a646ae51..ff8101b90 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,41 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 -# -# Sony KD-55AG8 Smart TV -# +# +# Model TCL 50P715X1 +# name: Power type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 15 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 # name: Mute type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 14 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 # name: Vol_up type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 12 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 # name: Vol_dn type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 13 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 # name: Ch_next type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 10 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 # name: Ch_prev type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 11 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 From 4c2e99799738d4d41aaf5b2e6e524c98794f8f83 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Fri, 9 Feb 2024 15:20:54 +0300 Subject: [PATCH 27/36] Disable expansion interrupt if the handle was acquired --- targets/f7/furi_hal/furi_hal_serial_control.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index 11ef23a6f..d2ab414c2 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -120,16 +120,21 @@ static bool furi_hal_serial_control_handler_stop(void* input, void* output) { static bool furi_hal_serial_control_handler_acquire(void* input, void* output) { FuriHalSerialId serial_id = *(FuriHalSerialId*)input; - if(furi_hal_serial_control->handles[serial_id].in_use) { + FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[serial_id]; + + if(handle->in_use) { *(FuriHalSerialHandle**)output = NULL; } else { // Logging if(furi_hal_serial_control->log_config_serial_id == serial_id) { furi_hal_serial_control_log_set_handle(NULL); + // Expansion + } else if(furi_hal_serial_control->expansion_serial == handle) { + furi_hal_serial_control_enable_expansion_irq(handle, false); } // Return handle - furi_hal_serial_control->handles[serial_id].in_use = true; - *(FuriHalSerialHandle**)output = &furi_hal_serial_control->handles[serial_id]; + handle->in_use = true; + *(FuriHalSerialHandle**)output = handle; } return true; @@ -143,9 +148,12 @@ static bool furi_hal_serial_control_handler_release(void* input, void* output) { furi_hal_serial_deinit(handle); handle->in_use = false; - // Return back logging if(furi_hal_serial_control->log_config_serial_id == handle->id) { + // Return back logging furi_hal_serial_control_log_set_handle(handle); + } else if(furi_hal_serial_control->expansion_serial == handle) { + // Re-enable expansion + furi_hal_serial_control_enable_expansion_irq(handle, true); } return true; From 73deff88635e061188d851a6755a0f65a02fd1dd Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Fri, 9 Feb 2024 16:31:45 +0300 Subject: [PATCH 28/36] Do not use a timer callback --- applications/services/expansion/expansion.c | 4 ++-- .../services/expansion/expansion_worker.c | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 99cdd984f..8f260f915 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -57,7 +57,7 @@ static void expansion_detect_callback(void* context) { ExpansionMessage message = { .type = ExpansionMessageTypeModuleConnected, - .api_lock = NULL, // Not locking the API here + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; // Not waiting for available queue space, discarding message if there is none @@ -71,7 +71,7 @@ static void expansion_worker_callback(void* context) { ExpansionMessage message = { .type = ExpansionMessageTypeModuleDisconnected, - .api_lock = NULL, // Not locking the API here + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index c2219a40d..fd92063d2 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -317,18 +317,6 @@ static inline void expansion_worker_state_machine(ExpansionWorker* instance) { } } -static void expansion_worker_pending_callback(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - ExpansionWorker* instance = context; - furi_thread_join(instance->thread); - - if(instance->callback != NULL) { - instance->callback(instance->cb_context); - } -} - static int32_t expansion_worker(void* context) { furi_assert(context); ExpansionWorker* instance = context; @@ -361,9 +349,9 @@ static int32_t expansion_worker(void* context) { furi_hal_serial_control_release(instance->serial_handle); furi_hal_power_insomnia_exit(); - // Do not invoke pending callback on user-requested exit - if(instance->exit_reason != ExpansionWorkerExitReasonUser) { - furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); + // Do not invoke worker callback on user-requested exit + if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { + instance->callback(instance->cb_context); } return 0; @@ -385,6 +373,7 @@ ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { void expansion_worker_free(ExpansionWorker* instance) { furi_stream_buffer_free(instance->rx_buf); + furi_thread_join(instance->thread); furi_thread_free(instance->thread); free(instance); } From cf41c98d667707391947431a732eab95b64d2ca7 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 9 Feb 2024 17:04:29 +0000 Subject: [PATCH 29/36] nfc app: fix retry scene navigation logic --- applications/main/nfc/scenes/nfc_scene_retry_confirm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index b6ad8144d..579373624 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -31,8 +31,10 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSlixUnlock)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSlixUnlock); - } else if(scene_manager_has_previous_scene( - nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + } else if( + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicDictAttack); } else if(scene_manager_has_previous_scene( From edda4e5508d48507d5d10c47814792cdee967c53 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 9 Feb 2024 18:32:40 +0000 Subject: [PATCH 30/36] nfc app: fix navigation in exit confirm scene --- applications/main/nfc/scenes/nfc_scene_exit_confirm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 16593cc89..7c4a3d19d 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -31,6 +31,13 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSelectProtocol); + } else if( + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { + const uint32_t possible_scenes[] = {NfcSceneReadMenu, NfcSceneSavedMenu}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + nfc->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); From b135c8e792d486133fecbe16973309dd4bcee0b1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 9 Feb 2024 22:00:24 +0300 Subject: [PATCH 31/36] Revert "NFC App: Generate MF Classic with custom UID added" This reverts commit 941652ec573c21a9e4d914b3c9dbb1372e9ff75d. --- applications/main/nfc/scenes/nfc_scene_set_type.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index 21c0d91db..b5102f801 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -63,15 +63,6 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; - } else if( - (event.event == NfcDataGeneratorTypeMfClassic1k_4b) || - (event.event == NfcDataGeneratorTypeMfClassic1k_7b) || - (event.event == NfcDataGeneratorTypeMfClassic4k_4b) || - (event.event == NfcDataGeneratorTypeMfClassic4k_7b) || - (event.event == NfcDataGeneratorTypeMfClassicMini)) { - nfc_data_generator_fill_data(event.event, instance->nfc_device); - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - consumed = true; } else { nfc_data_generator_fill_data(event.event, instance->nfc_device); scene_manager_set_scene_state( From 7ab888bb98a0dd81f739557f6bb21783813935bf Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 9 Feb 2024 22:00:47 +0300 Subject: [PATCH 32/36] Revert "NFC: Add manually MF Classic UID desync bug fixed" This reverts commit d1df26cc830c014b7a8dcabe3a98f6ad08e5bc78. --- .../main/nfc/scenes/nfc_scene_set_uid.c | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 7376ce0bc..df8a4dc72 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -2,29 +2,6 @@ #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" -// Sync UID from #UID to block 0 data -void mfclassic_sync_uid(NfcDevice* instance) { - size_t uid_len; - const uint8_t* uid = nfc_device_get_uid(instance, &uid_len); - - MfClassicData* mfc_data = (MfClassicData*)nfc_device_get_data(instance, NfcProtocolMfClassic); - uint8_t* block = mfc_data->block[0].data; - - // Sync UID - for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { - block[i] = uid[i]; - } - - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { - block[uid_len] ^= block[i]; - } - } -} - static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { NfcApp* instance = context; // Retrieve previously saved UID length @@ -68,9 +45,6 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { consumed = true; } } else { - if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolMfClassic) - mfclassic_sync_uid(instance->nfc_device); - scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; } From 1a02152d03a86baec0e0ad8b33a968880207a9aa Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Feb 2024 14:46:46 +0300 Subject: [PATCH 33/36] Update ndef parser, apply mf classic dict changes by @Willy-JL https://github.com/Flipper-XFW/Xtreme-Firmware/blob/dev/applications/main/nfc/plugins/supported_cards/ndef.c https://github.com/Flipper-XFW/Xtreme-Firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc --- .../main/nfc/plugins/supported_cards/ndef.c | 30 +- .../resources/nfc/assets/mf_classic_dict.nfc | 1617 +++++++++++------ 2 files changed, 1066 insertions(+), 581 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index a40f59a16..1a2497f2c 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -362,20 +362,26 @@ static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) { // Memory layout documentation: // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nfc/doc/type_2_tag.html#id2 - // Double check static values layout - // First 4 static reserved pages for UID, internal and lock bytes - // (Not sure if NDEF cata can be found in cards with different layout) - if(data->page[0].data[0] != 0x04) break; - if(data->page[2].data[1] != 0x48) break; // Internal - if(data->page[2].data[2] != 0x00) break; // Lock bytes - if(data->page[2].data[3] != 0x00) break; // ... - if(data->page[3].data[0] != 0xE1) break; // Capability container - if(data->page[3].data[1] != 0x10) break; // ... + // Check card type can contain NDEF + if(data->type != MfUltralightTypeNTAG203 && data->type != MfUltralightTypeNTAG213 && + data->type != MfUltralightTypeNTAG215 && data->type != MfUltralightTypeNTAG216 && + data->type != MfUltralightTypeNTAGI2C1K && data->type != MfUltralightTypeNTAGI2C2K) { + break; + } - // Data content starts here at 5th page + // Double check Capability Container (CC) and find data area bounds + struct { + uint8_t nfc_magic_number; + uint8_t document_version_number; + uint8_t data_area_size; + uint8_t read_write_access; + }* cc = (void*)&data->page[3].data[0]; + if(cc->nfc_magic_number != 0xE1) break; + if(cc->document_version_number != 0x10) break; const uint8_t* cur = &data->page[4].data[0]; - const uint8_t* end = &data->page[0].data[0] + - (mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE); + const uint8_t* end = cur + (cc->data_area_size * 2 * MF_ULTRALIGHT_PAGE_SIZE); + size_t max_size = mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE; + end = MIN(end, &data->page[0].data[0] + max_size); size_t message_num = 0; // Parse as TLV (see docs above) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 9453b8a3f..609b48c09 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1,90 +1,145 @@ ########################### # Do not edit, this file will be overwritten after firmware update # Use the user_dict file for user keys -# Last update 19th October, 2022 -# ------------------------- -# MIFARE DEFAULT KEYS -# -- ICEMAN FORK VERSION -- -# -- CONTRIBUTE TO THIS LIST, SHARING IS CARING -- -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic -# DEFAULTKEY(FIRSTKEYUSEDBYPROGRAMIFNOUSERDEFINEDKEY) +# Last updated 17 December 2023 +########################### +# +-----------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic | +# +-----------------------------------------------------------------------------------------------------+ +# Mifare Default Keys +# -- iceman fork version -- +# -- contribute to this list, sharing is caring -- +# Default key FFFFFFFFFFFF -# BLANKKEY +# Blank key 000000000000 -# NFC FORUM MADKEY -# MAD ACCESS KEY A (REVERSED) +# NFC Forum MADkey +A0A1A2A3A4A5 +# MAD access key A (reversed) A5A4A3A2A1A0 -# MAD ACCESS KEY B +# MAD access key B 89ECA97F8C2A -# KEY A WIEN -# KEY B WIEN -# ICOPY-X +# Mifare 1k EV1 (S50) hidden blocks, Signature data +# 16 A +5C8FF9990DA2 +# 17 A +75CCB59C9BED +# 16 B +D01AFEEB890A +# 17 B +4B791BEA7BCC +# QL88 keys +# 17 A/B +2612C6DE84CA +707B11FC1481 +# QL88 diversifed +03F9067646AE +2352C5B56D85 +B0B1B2B3B4B5 +C0C1C2C3C4C5 +D0D1D2D3D4D5 +AABBCCDDEEFF +4D3A99C351DD +1A982C7E459A +# key A Wien +D3F7D3F7D3F7 +# key B Wien +5A1B85FCE20A +714C5C886E97 +587EE5F9350F +A0478CC39091 +533CB6C723F6 +8FD0A4F256E9 +# iCopy-X E00000000000 E7D6064C5860 B27CCAB30DBD -# LIB / NAT BIEB +# lib / Nat Bieb D2ECE8B9395E -# NSCP DEFAULT KEY +# NSCP default key 1494E81663D7 -# KIEV KEYS +# NFC tools +7C9FB8474242 +# Kiev keys 569369C5A0E5 632193BE1C3C 644672BD4AFE 8FE644038790 9DE89E070277 +B5FF67CBA951 EFF603E1EFE9 F14EE7CAE863 -# KIEV / OV-CHIPKAART -B5FF67CBA951 +# ICT S14 A/B +9C28A60F7249 +C9826AF02794 # RKF -# VÄSTTRAFIKEN KEYA, RKF ÖSTGÖTATRAFIKEN KEYA +# Vasttrafiken KeyA, RKF OstgotaTrafiken KeyA FC00018778F7 +# Vasttrafiken KeyA 0297927C0F77 54726176656C -# VÄSTTRAFIKEN KEYB +# Vasttrafiken KeyB 00000FFE2488 776974687573 EE0042F88840 -# RKF SLKEYA +# RKF SLKeyA 26940B21FF5D A64598A77478 -# RKF SLKEYB +# RKF SLKeyB 5C598C9C58B5 E4D2770A89BE -# RKF REJSKORTDANMARK KEYA +# RKF Rejskort Danmark KeyA 722BFCC5375F +# RKF Rejskort Danmark KeyB F1D83F964314 -# RKF JOJOPRIVAKEYA +# RKF JOJOPRIVA KeyA 505249564141 -# RKF JOJOPRIVAKEYB +# RKF JOJOPRIVA KeyB 505249564142 -# RKF JOJOGROUPKEYA +# RKF JOJOGROUP KeyA 47524F555041 -# RKF JOJOGROUPKEYB -47524F555042 434F4D4D4F41 +# RKF JOJOGROUP KeyB +47524F555042 434F4D4D4F42 +# TNP3xxx 4B0B20107CCB -# TNP3XXX -# ACCESS CONTROL SYSTEM +# Access control system 605F5E5D5C5B -# MORE KEYS FROM MFC_DEFAULT_KEYS.LUA +# NSP Global keys A and B (uk housing access control) +199404281970 +199404281998 +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=25925#p25925 +# Tengo Cards Key A +FFF011223358 +FF9F11223358 +# Elevator system Kherson, Ukraine +AC37E76385F5 +576DCFFF2F25 +# Car wash system +1EE38419EF39 +26578719DCD9 +# more Keys from mfc_default_keys.lua 000000000001 000000000002 00000000000A 00000000000B +010203040506 0123456789AB 100000000000 111111111111 +123456789ABC 12F2EE3478C1 14D446E33363 1999A3554A55 200000000000 222222222222 27DD91F1FCF1 -# DIRECTORYANDEVENTLOGKEYB +# Hotel system +505209016A1F +# Directory and eventlog KeyB 2BA9621E0A36 -# DIRECTORYANDEVENTLOGKEYA +# Directory and eventlog KeyA 4AF9D7ADEBE4 333333333333 33F974B42769 @@ -104,7 +159,8 @@ A00000000000 A053A292A4AF A94133013401 AAAAAAAAAAAA -# KEYFROMLADYADA.NET +# Key from ladyada.net +ABCDEF123456 B00000000000 B127C6F41436 BBBBBBBBBBBB @@ -113,25 +169,24 @@ C934FE34D934 CCCCCCCCCCCC DDDDDDDDDDDD EEEEEEEEEEEE -# ELEVATOR -# DATA FROM FORUM +# elevator +# data from forum FFFFFF545846 F1A97341A9FC -# HOTEL SYSTEM +# hotel system 44AB09010845 85FED980EA5A -# ARD (FR) KEY A +# ARD (fr) key A 43454952534E -# ARD (FR) KEY B +# ARD (fr) key B 4A2B29111213 4143414F5250 -# TEHRAN RAILWAY +# Tehran Railway A9B43414F585 1FB235AC1388 -# DATA FROM HTTP://IRQ5.IO/2013/04/13/DECODING-BCARD-CONFERENCE-BADGES/ -# BCARD KEYB +# Data from http://irq5.io/2013/04/13/decoding-bcard-conference-badges/ +# BCARD KeyB F4A9EF2AFC6D -# DATA FROM ... # S0 B 89EAC97F8C2A # S4 A @@ -141,12 +196,13 @@ F4A9EF2AFC6D # S6 B FB0B20DF1F34 A9F953DEF0A3 +# Data from forum 74A386AD0A6D 3F7A5C2DBD81 21EDF95E7433 C121FF19F681 3D5D9996359A -# HERE BE BIP KEYS... +# Here be BIP keys... 3A42F33AF429 1FC235AC1309 6338A371C0ED @@ -179,32 +235,46 @@ D49E2826664F 51284C3686A6 3DF14C8000A1 6A470D54127C -# DATA FROM HTTP://PASTEBIN.COM/AK9BFTPW -# LÄNSTRAFIKEN I VÄSTERBOTTEN +# Data from http://pastebin.com/AK9Bftpw +# Lanstrafiken i Vasterbotten 48FFE71294A0 E3429281EFC1 16F21A82EC84 460722122510 -# 3DPRINTER -# EPI ENVISIONTE 3DPRINTER +# 3dprinter +# EPI Envisionte AAFB06045877 -# GYM -# FYSIKEN A +# gym +# Fysiken A 3E65E4FB65B3 -# FYSIKEN B +# Fysiken B 25094DF6F148 -# CLEVERFIT +# https://mattionline.de/fitnessstudio-armband-reverse-engineering/ +# https://mattionline.de/milazycracker/ +# gym wistband A, same as Fysiken A +# gym wistband B +81CC25EBBB6A +195DC63DB3A3 +# CleverFit A05DBD98E0FC -# HOTEL KEYCARD +# GoFit +AA4DDA458EBB +EAB8066C7479 +# Nordic Wellness A, same as Fysiken A +# Nordic Wellness B +E5519E1CC92B +# Hotel KeyCard D3B595E9DD63 AFBECD121004 -# SIMONSVOSS +# SimonsVoss 6471A5EF2D1A # ID06 4E3552426B32 22BDACF5A33F 6E7747394E63 763958704B78 +# Onity S1 A/B +8A19D40CF2B5 # 24-7 D21762B2DE3B 0E83A374B513 @@ -218,9 +288,9 @@ F101622750B7 710732200D34 7C335FB121B5 B39AE17435DC -# KEY A +# key A 454841585443 -# DATA FROM HTTP://PASTEBIN.COM/GQ6NK38G +# Data from http://pastebin.com/gQ6nk38G D39BB83F5297 85675B200017 528C9DFFE28C @@ -245,12 +315,10 @@ FEE470A4CB58 75EDE6A84460 DF27A8F1CB8E B0C9DD55DD4D -# DATA FROM HTTP://BIT.LY/1BDSBJL +# Data from http://bit.ly/1bdSbJl A0B0C0D0E0F0 A1B1C1D1E1F1 -# DATA FROM MSK SOCIAL -A229E68AD9E5 -49C2B5296EF4 +# Data from msk social 2735FC181807 2ABA9519F574 84FD7F7A12B6 @@ -274,7 +342,9 @@ C7C0ADB3284F D8A274B2E026 B20B83CB145C 9AFA6CB4FC3D -# DATA FROM HTTP://PASTEBIN.COM/RRJUEDCM +A229E68AD9E5 +49C2B5296EF4 +# Data from http://pastebin.com/RRJUEDCM 0D258FE90296 E55A3CA71826 A4F204203F56 @@ -287,17 +357,17 @@ EEB420209D0C 1ACC3189578C C2B7EC7D4EB1 369A4663ACD2 -# DATA FROM HTTPS://GITHUB.COM/ZHANGJINGYE03/ZXCARDUMPER -# ZXCARD KEY A/B +# Data from https://github.com/zhangjingye03/zxcardumper +# zxcard Key A/B 668770666644 003003003003 -# DATA FROM HTTP://PHREAKERCLUB.COM/FORUM/SHOWTHREAD.PHP?P=41266 +# Data from http://phreakerclub.com/forum/showthread.php?p=41266 26973EA74321 71F3A315AD26 51044EFB5AAB AC70CA327A04 EB0A8FF88ADE -# TRANSPORT SYSTEM METROMONEY +# Transport system Metromoney 2803BCB0C7E1 9C616585E26D 4FA9EB49F75E @@ -305,7 +375,7 @@ EB0A8FF88ADE A160FCD5EC4C 112233445566 361A62F35BC9 -# TRANSPORT SYSTEM SPAIN +# Transport system Spain 83F3CB98C258 070D486BC555 A9B018868CC1 @@ -338,15 +408,89 @@ C52876869800 5145C34DBA19 25352912CD8D 81B20C274C3F -# DATA FROM MALL -# PLAYLAND BALIKESIR +00B70875AF1D +04B787B2F3A5 +05412723F1B6 +05C301C8795A +066F5AF3CCEE +0A1B6C50E04E +0AD0956DF6EE +0AD6B7E37183 +0F3A4D48757B +1417E5671417 +18AB07270506 +18E887D625B4 +1ABC15934F5A +1AF66F83F5BE +260480290483 +2900AAC52BC3 +2910AFE15C99 +374521A38BCC +3A4C47757B07 +3A524B7A7B37 +3C4ABB877EAF +3F3A534B7B7B +4B787B273A50 +4B92DF1BF25D +4F0E4AE8051A +514B797B2F3A +529CF51F05C5 +52B26C199862 +57A18BFEC381 +5A7D87876EA8 +64CBADC7A313 +65B6C3200736 +67B1B3A4E497 +6B0454D5D3C3 +6B3B7AF45777 +6C273F431564 +702C1BF025DD +738385948494 +76E450094393 +777B1F3A4F4A +7B173A4E4976 +81504133B13C +826576A1AB68 +8A55194F6587 +8DFACF11E778 +8FD6D76742DC +9AFEE1F65742 +9D56D83658AC +9FAC23197904 +A1AB3A08712C +A514B797B373 +A58AB5619631 +A5BB18152EF1 +A777B233A4F4 +AB19BC885A29 +AB91BDA25F00 +AE98BA1E6F2C +B133A4D48757 +B3A4C47757B0 +B6803136F5AF +B793ADA6DB0C +B95BFDEBA7E4 +C0AA2BBD27CD +C27F5C1A9C2B +C9BE49675FE4 +CCCE24102003 +CDE668FDCDBA +D23A31A4AAB9 +DEDD7688BC38 +E9AE90885C39 +F0A3C5182007 +F3A524B7A7B3 +# Data from mall +# playland balikesir ABBA1234FCB0 -# A TRIO BOWLING BAHCELIEVLER +# A trio bowling bahcelievler 314F495254FF 4152414B4E41 -# KARINCA PARK NIGDE +# karinca park nigde 4E474434FFFF -# DATA FROM HTTPS://GITHUB.COM/RADIOWAR/NFCGUI +# hotel system +537930363139 +# Data from https://github.com/RadioWar/NFCGUI 44DD5A385AAF 21A600056CB0 B1ACA33180A5 @@ -383,11 +527,13 @@ CBA6AE869AD5 A7ABBC77CC9E F792C4C76A5C BFB6796A11DB -# DATA FROM SALTO A/B +# Data from Salto A/B 6A1987C40A21 7F33625BC129 +6BE9314930D8 +# Data from forum 2338B4913111 -# DATA FROM STOYE +# Data from stoye CB779C50E1BD A27D3804C259 003CC420001A @@ -414,37 +560,34 @@ D9A37831DCE5 C5CFE06D9EA3 C0DECE673829 A56C2DF9A26D -# DATA FROM HTTPS://PASTEBIN.COM/VBWAST74 +# Data from https://pastebin.com/vbwast74 68D3F7307C89 -# SMART RIDER. WESTERN AUSTRALIAN PUBLIC TRANSPORT CARDS +# Smart Rider. Western Australian Public Transport Cards 568C9083F71C -# BANGKOK METRO KEY +117E5C165B10 +24BB421C7973 +3E3A546650EA +41F262D3AB66 +514956AB3142 +863933AE8388 +# Bangkok metro key 97F5DA640B18 -# METRO VALENCIA KEY +# Metro Valencia key A8844B0BCA06 -# HTC EINDHOVEN KEY +# HTC Eindhoven key 857464D3AAD1 -# VIGIK KEYS -# VARIOUS SOURCES : -# * HTTPS://GITHUB.COM/DUMPDOS/VIGIK -# * HTTP://NEWFFR.COM/VIEWTOPIC.PHP?&FORUM=235&TOPIC=11559 -# * OWN DUMPS -# FRENCH VIGIK +# Vigik Keys +# Various sources : +# * https://github.com/DumpDos/Vigik +# * http://newffr.com/viewtopic.php?&forum=235&topic=11559 +# * Own dumps +# French VIGIK # VIGIK1 A 314B49474956 # VIGIK1 B 564C505F4D41 BA5B895DA162 -# VIGIK MYSTERY KEYS MIFARE 1K EV1 (S50) -# 16 A -5C8FF9990DA2 -# 17 A -75CCB59C9BED -# 16 B -D01AFEEB890A -# 17 B -4B791BEA7BCC -# BTCINO UNDETERMINED SPREAKD 0X01->0X13 KEY +# BTCINO UNDETERMINED SPREAKD 0x01->0x13 key 021209197591 2EF720F2AF76 414C41524F4E @@ -452,8 +595,8 @@ D01AFEEB890A 4A6352684677 BF1F4424AF76 536653644C65 -# INTRATONE COGELEC -# DATA FROM HTTP://BOUZDECK.COM/RFID/32-CLONING-A-MIFARE-CLASSIC-1K-TAG.HTML +# Intratone Cogelec +# Data from http://bouzdeck.com/rfid/32-cloning-a-mifare-classic-1k-tag.html 484558414354 A22AE129C013 49FAE4E3849F @@ -470,7 +613,7 @@ E64A986A5D94 66D2B7DC39EF 6BC1E1AE547D 22729A9BD40F -# DATA FROM HTTPS://DFIR.LU/BLOG/CLONING-A-MIFARE-CLASSIC-1K-TAG.HTML +# Data from https://dfir.lu/blog/cloning-a-mifare-classic-1k-tag.html 925B158F796F FAD63ECB5891 BBA840BA1C57 @@ -485,9 +628,9 @@ CC6B3B3CD263 703140FD6D86 157C9A513FA5 E2A5DC8E066F -# DATA FROM FORUM, SCHLAGE 9691T FOB +# Data from forum, schlage 9691T fob EF1232AB18A0 -# DATA FROM A OYSTER CARD +# Data from a oyster card 374BF468607F BFC8E353AF63 15CAFD6159F6 @@ -517,9 +660,9 @@ A2ABB693CE34 91F93A5564C9 E10623E7A016 B725F9CBF183 -# DATA FROM FDI TAG +# Data from FDi tag 8829DA9DAF76 -# DATA FROM GITHUB ISSUE +# Data from GitHub issue 0A7932DC7E65 11428B5BCE06 11428B5BCE07 @@ -529,7 +672,6 @@ B725F9CBF183 11428B5BCE0F 18971D893494 25D60050BF6E -3FA7217EC575 44F0B5FBE344 7B296F353C6B 8553263F4FF0 @@ -543,15 +685,18 @@ D4FE03CE5B09 D4FE03CE5B0A D4FE03CE5B0F E241E8AFCBAF -# DATA FROM FORUM POST +# Transport system Argentina - SUBE +# Shared key - sec 3 blk 15 +3FA7217EC575 +# Data from forum post 123F8888F322 050908080008 -# DATA FROM HOIST +# Data from hoist 4F9F59C9C875 -# DATA FROM PASTEBIN +# Data from pastebin 66F3ED00FED7 F7A39753D018 -# DATA FROM HTTPS://PASTEBIN.COM/Z7PEEZIF +# Data from https://pastebin.com/Z7pEeZif 386B4D634A65 666E564F4A44 564777315276 @@ -582,24 +727,24 @@ F7A39753D018 6F506F493353 31646241686C 77646B633657 -# DATA FROM TRANSPERT +# Data from TransPert 2031D1E57A3B 53C11F90822A 9189449EA24E -# DATA FROM GITHUB +# data from Github 410B9B40B872 2CB1A90071C8 -# DATA FROM 8697389ACA26 1AB23CD45EF6 013889343891 0000000018DE 16DDCB6B3F24 -# DATA FROM HTTPS://PASTEBIN.COM/VWDRZW7D -# VINGCARD MIFARE 4K STAFF CARD +# Data from https://pastebin.com/vwDRZW7d +# Vingcard Mifare 4k Staff card EC0A9B1A9E06 6C94E1CED026 0F230695923F +0000014B5C31 BEDB604CC9D1 B8A1F613CF3D B578F38A5C61 @@ -608,34 +753,36 @@ B66AC040203A 2E641D99AD5B AD4FB33388BF 69FB7B7CD8EE -# HOTEL +# Hotel 2A6D9205E7CA +13B91C226E56 +# KABA Hotel Locks 2A2C13CC242A 27FBC86A00D0 01FA3FC68349 +# Smart Rider. Western Australian Public Transport Cards 6D44B5AAF464 1717E34A7A8A -# RFIDEAS +# RFIDeas 6B6579737472 -# HID MIFARE CLASSIC 1K KEY +# HID MIFARE Classic 1k Key 484944204953 204752454154 # HID MIFARE SO 3B7E4FD575AD 11496F97752A -# LUXEO/AZTEK CASHLESS VENDING +# Luxeo/Aztek cashless vending 415A54454B4D # BQT 321958042333 -# APERIO KEY_A SECTOR 1, 12, 13, 14, 15 DATA START 0 LENGTH 48 +# Aperio KEY_A Sector 1, 12, 13, 14, 15 Data Start 0 Length 48 160A91D29A9C -# GALLAGHER +# Gallagher B7BF0C13066E -# PIK COMFORT MOSCOW KEYS (ISBC MIFARE PLUS SE 1K) +# PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) 009FB42D98ED 002E626E2820 -# BOSTON, MA, USA TRANSIT - MBTA CHARLIE CARD -# CHARLIE +# Boston, MA, USA Transit - MBTA Charlie Card 3060206F5B0A 5EC39B022F2B 3A09594C8587 @@ -670,13 +817,14 @@ D80511FC2AB4 BB467463ACD6 E67C8010502D FF58BA1B4478 -# DATA FROM HTTPS://PASTEBIN.COM/KZ8XP4EV +# Data from https://pastebin.com/Kz8xp4ev FBF225DC5D58 -# DATA HTTPS://PASTEBIN.COM/BEM6BDAE -# VINGCARD.TXT +# Data https://pastebin.com/BEm6bdAE +# vingcard.txt +# Note: most likely diversified +96A301BCE267 4708111C8604 3D50D902EA48 -96A301BCE267 6700F10FEC09 7A09CC1DB70A 560F7CFF2D81 @@ -688,17 +836,18 @@ FBF225DC5D58 D9BCDE7FC489 0C03A720F208 6018522FAC02 -# DATA FROM HTTPS://PASTEBIN.COM/4T2YFMGT -# MIFARE TECHNISCHE UNIVERSITÄT GRAZ TUG +# Data from https://pastebin.com/4t2yFMgt +# Mifare technische Universitat Graz TUG D58660D1ACDE 50A11381502C C01FC822C6E5 0854BF31111E -# MORE KEYS: -8A19D40CF2B5 +# More keys - Found 8A at Sebel Hotel in Canberra, Australia AE8587108640 +# SafLock standalone door locks 135B88A94B8B -# RUSSIAN TROIKA CARD +# Russian Troika card +EC29806D9738 08B386463229 0E8F64340BA4 0F1C63013DBA @@ -753,7 +902,10 @@ EAAC88E5DC99 F8493407799D 6B8BD9860763 D3A297DC2698 -# KEYS FROM MIFARECLASSICTOOL PROJECT +# Data from reddit +34635A313344 +593367486137 +# Keys from Mifare Classic Tool project 044CE1872BC3 045CECA15535 0BE5FAC8B06A @@ -808,15 +960,81 @@ FD8705E721B0 00ADA2CD516D 237A4D0D9119 0ED7846C2BC9 -# HOTEL ADINA +FFFFD06F83E3 +FFFFAE82366C +F89C86B2A961 +F83466888612 +ED3A7EFBFF56 +E96246531342 +E1DD284379D4 +DFED39FFBB76 +DB5181C92CBE +CFC738403AB0 +BCFE01BCFE01 +BA28CFD15EE8 +B0699AD03D17 +AABBCC660429 +A4EF6C3BB692 +A2B2C9D187FB +9B1DD7C030A1 +9AEDF9931EC1 +8F9B229047AC +872B71F9D15A +833FBD3CFE51 +5D293AFC8D7E +5554AAA96321 +474249437569 +435330666666 +1A2B3C4D5E6F +123456ABCDEF +83BAB5ACAD62 +64E2283FCF5E +64A2EE93B12B +46868F6D5677 +40E5EA1EFC00 +37D4DCA92451 +2012053082AD +2011092119F1 +200306202033 +1795902DBAF9 +17505586EF02 +022FE48B3072 +013940233313 +# Hotel Adina 9EBC3EB37130 -# MOST LIKELY DIVERSED INDIVIDUAL KEYS. -# DATA FROM HTTPS://GITHUB.COM/KORSEHINDI/PROXMARK3/COMMIT/24FDBFA9A1D5C996AAA5C192BC07E4AB28DB4C5C +# Misc. keys from hotels & library cards in Germany +914F57280CE3 +324A82200018 +370AEE95CD69 +2E032AD6850D +1FEDA39D38EC +288B7A34DBF8 +0965E3193497 +18C628493F7F +064D9423938A +995FD2A2351E +7C7D672BC62E +217250FB7014 +AE7478CCAEE7 +ABBF6D116EAF +05862C58EDFB +E43B7F185460 +6A59AA9A959B +B79E5B175227 +7BC9EBB8274B +B2AFBF2331D4 +223E5847DD79 +640524D2A39B +AEE297CB2FD6 +3DA5DFA54604 +0CF1A2AA1F8D +# most likely diversifed individual keys. +# data from https://github.com/korsehindi/proxmark3/commit/24fdbfa9a1d5c996aaa5c192bc07e4ab28db4c5c 491CDC863104 A2F63A485632 98631ED2B229 19F1FFE02563 -# ARGENTINA +# Argentina 563A22C01FC8 43CA22C13091 25094DF2C1BD @@ -888,7 +1106,7 @@ AE43F36C1A9A BE7C4F6C7A9A 5EC7938F140A 82D58AA49CCB -# MELONCARD +# MELON CARD 323334353637 CEE3632EEFF5 827ED62B31A7 @@ -904,7 +1122,7 @@ A7FB4824ACBF 00F0BD116D70 4CFF128FA3EF 10F3BEBC01DF -# TRANSPORTES INSULAR LA PALMA +# Transportes Insular La Palma 0172066B2F03 0000085F0000 1A80B93F7107 @@ -937,7 +1155,7 @@ B1A862985913 3B0172066B2F 3F1A87298691 F3F0172066B2 -# TEHRAN EZPAY +# Tehran ezpay 38A88AEC1C43 CBD2568BC7C6 7BCB4774EC8F @@ -953,11 +1171,11 @@ D3B1C7EA5C53 604AC8D87C7E 8E7B29460F12 BB3D7B11D224 -# CHACO +# Chaco B210CFA436D2 B8B1CFA646A8 A9F95891F0A4 -# KEYS FROM APK APPLICATION "SCAN BADGE" +# Keys from APK application "Scan Badge" 4A4C474F524D 444156494442 434143445649 @@ -976,7 +1194,7 @@ A0004A000036 DFE73BE48AC6 B069D0D03D17 000131B93F28 -# FROM THE DFW AREA, TX, USA +# From the DFW Area, TX, USA A506370E7C0F 26396F2042E7 70758FDD31E0 @@ -992,7 +1210,7 @@ EF4C5A7AC6FC B47058139187 8268046CD154 67CC03B7D577 -# FROM THE HTL MÖDLING, NÖ, AT +# From the HTL Modling, NO, AT A5524645CD91 D964406E67B4 99858A49C119 @@ -1006,27 +1224,28 @@ C27D999912EA E45230E7A9E8 535F47D35E39 FB6C88B7E279 -# METRO CARD, AT +# Metro Card, AT 223C3427108A -# UNKNOWN, AT +# Unknown, AT 23D4CDFF8DA3 E6849FCC324B 12FD3A94DF0E +# Unknown, AT 0B83797A9C64 39AD2963D3D1 -# HOTEL BERLIN CLASSIC ROOM A KEY +# Hotel Berlin Classic room A KEY 34B16CD59FF8 -# HOTEL BERLIN CLASSIC ROOM B KEY +# Hotel Berlin Classic room B KEY BB2C0007D022 -# COINMATIC LAUNDRY SMART CARD -# DATA FROM: HTTPS://PASTEBIN.COM/XZQILTUF +# Coinmatic laundry Smart card +# data from: https://pastebin.com/XZQiLtUf 0734BFB93DAB 85A438F72A8A -# DATA FROM FORUM, CHINESE HOTEL +# Data from forum, Chinese hotel 58AC17BF3629 B62307B62307 A2A3CCA2A3CC -# GRANADA, ES TRANSPORT CARD +# Granada, ES Transport Card 000000270000 0F385FFB6529 29173860FC76 @@ -1043,14 +1262,14 @@ B385EFA64290 C9739233861F F3864FCCA693 FC9839273862 -# VARIOUS HOTEL KEYS +# various hotel keys 34D3C568B348 91FF18E63887 4D8B8B95FDEE 354A787087F1 4A306E62E9B6 B9C874AE63D0 -# DATA FROM OFFICIAL REPO +# Data from official repo F00DFEEDD0D0 0BB31DC123E5 7578BF2C66A9 @@ -1067,19 +1286,20 @@ B8937130B6BA D7744A1A0C44 82908B57EF4F FE04ECFE5577 -# COMFORT INN HOTEL +# comfort inn hotel 4D57414C5648 4D48414C5648 -# UNKNOWN HOTEL KEY +# unknown hotel key 6D9B485A4845 -# BOSCH SOLUTION 6000 -# FOUND IN TAGINFO APP -# RATB KEY +# Bosch Solution 6000 +5A7A52D5E20D +# Found in TagInfo app +# RATB key C1E51C63B8F5 1DB710648A65 -# E-GO CARD KEY +# E-GO card key 18F34C92A56E -# LIBRARY CARD MFP - SL1 +# Library Card MFP - SL1 4A832584637D CA679D6291B0 30D9690FC5BC @@ -1094,7 +1314,7 @@ AADE86B1F9C1 C67BEB41FFBF B84D52971107 52B0D3F6116E -# DATA FROM HTTPS://PASTEBIN.COM/CLSQQ9XN +# Data from https://pastebin.com/cLSQQ9xN CA3A24669D45 4087C6A75A96 403F09848B87 @@ -1102,28 +1322,28 @@ D73438698EEA 5F31F6FCD3A0 A0974382C4C5 A82045A10949 -# DATA FROM HTTPS://PASTEBIN.COM/2IV8H93H -# FUNNIVARIUM -# FORUM ANKARA +# Data from https://pastebin.com/2iV8h93h +# funnivarium +# forum ankara 2602FFFFFFFF -# MACERA ADASI -# ANKARA KENTPARK +# macera adasi +# ankara kentpark # INACTIVE 0A4600FF00FF DFF293979FA7 4D6F62692E45 4118D7EF0902 -# PETROL OFISI -# POSITIVE CARD -# ODE-GEC +# petrol ofisi +# positive card +# ode-gec 0406080A0C0E -# KONYA ELKART +# konya elkart 988ACDECDFB0 120D00FFFFFF -# BOWLINGO -# SERDIVAN AVYM +# bowlingo +# serdivan avym 4AE23A562A80 -# KART54 +# kart 54 2AFFD6F88B97 A9F3F289B70C DB6819558A25 @@ -1131,28 +1351,28 @@ DB6819558A25 B16B2E573235 42EF7BF572AB 274E6101FC5E -# CRAZY PARK -# KIZILAY AVM +# crazy park +# kizilay avm 00DD300F4F10 -# KARTSISTEM B +# kartsistem B FEE2A3FBC5B6 -# TORU ENT -# TAURUS AVM +# toru ent +# taurus avm 005078565703 -# VING? +# Ving? 0602721E8F06 FC0B50AF8700 F7BA51A9434E -# ESKART -# ESKISEHIR TRANSPORT CARD +# eskart +# eskisehir transport card E902395C1744 4051A85E7F2D 7357EBD483CC D8BA1AA9ABA0 76939DDD9E97 3BF391815A8D -# MUZEKART -# MUSEUM CARD FOR TURKEY +# muzekart +# museum card for turkey 7C87013A648A E8794FB14C63 9F97C182585B @@ -1182,8 +1402,8 @@ D0DDDF2933EC 240F0BB84681 9E7168064993 2F8A867B06B4 -# BURSAKART -# BURSA TRANSPORT CARD +# bursakart +# bursa transport card 755D49191A78 DAC7E0CBA8FD 68D3263A8CD6 @@ -1191,20 +1411,20 @@ DAC7E0CBA8FD 0860318A3A89 1927A45A83D3 B2FE3B2875A6 -# PLAYLAND -# MALTEPE PARK +# playland +# maltepe park ABCC1276FCB0 AABAFFCC7612 -# LUNASAN -# KOCAELI FAIR +# lunasan +# kocaeli fair 26107E7006A0 -# GAMEFACTORY -# OZDILEK +# gamefactory +# ozdilek 17D071403C20 534F4C415249 534F4C303232 -# NESPRESSO, SMART CARD -# KEY-GEN ALGO, THESE KEYS ARE FOR ONE CARD +# Nespresso, smart card +# key-gen algo, these keys are for one card (keys diversified) FF9A84635BD2 6F30126EE7E4 6039ABB101BB @@ -1281,17 +1501,19 @@ AE76242931F1 124578ABFEDC ABFEDC124578 4578ABFEDC12 -# PREMIER INN HOTEL CHAIN +# Data from +# premier inn hotel chain 5E594208EF02 AF9E38D36582 -# NORWEGIAN BUILDING SITE IDENTICATION CARD. (HMS KORT) +# Norwegian building site identication card. (HMS KORT) +# Key a 10DF4D1859C8 -# KEY B +# Key B B5244E79B0C8 -# UKRAINE HOTEL +# Ukraine hotel F5C1C4C5DE34 -# DATA FROM MIFARE CLASSIC TOOL REPO -# ROTTERDAM UNIVERSITY OF APPLIED SCIENCES CAMPUS CARD +# Data from Mifare Classic Tool repo +# Rotterdam University of applied sciences campus card BB7923232725 A95BD5BB4FC5 B099335628DF @@ -1314,7 +1536,8 @@ B5ADEFCA46C4 BF3FE47637EC B290401B0CAD AD11006B0601 -# ARMENIAN METRO +# Data from Mifare Classic Tool repo +# Armenian Metro E4410EF8ED2D 6A68A7D83E11 0D6057E8133B @@ -1323,7 +1546,7 @@ D3F3B958B8A3 2196FAD8115B 7C469FE86855 CE99FBC8BD26 -# KEYS FROM EUROTHERMES GROUP (SWITZERLAND) +# keys from Eurothermes group (Switzerland) D66D91829013 75B691829013 83E391829013 @@ -1338,18 +1561,134 @@ FED791829013 29A791829013 668091829013 00008627C10A -# KEYS FROM NSP MANCHESTER UNIVERSITY UK ACCOMODATION STAFF AND STUDENTS -199404281970 -199404281998 -# EASYCARD +# easycard 310D51E539CA 2CCDA1358323 03E0094CEDFE 562E6EF73DB6 F53E9F4114A9 AD38C17DE7D2 -# SOME KEYS OF HTTPS://W3BSIT3-DNS.COM AND HTTPS://IKEY.RU -# STRELKA EXTENSION +# SUBE cards keys (new) +2DEB57A3EA8F +32C1BB023F87 +70E3AD3F2D29 +202ECDCCC642 +3686192D813F +24501C422387 +2C7813A721C3 +FFE04BE3D995 +D28F090677A1 +DE2D83E2DCCC +A66A478712EA +643232ADB2D5 +C7F4A4478415 +95C013B70D99 +3C383889362A +3C6D9C4A90FA +51BEDBA005E5 +74BF7363F354 +53B09DB89111 +E98075318085 +2F904641D75F +7F60AEF68136 +F5C1B3F62FDA +3E6E5713BA10 +8B75A29D4AB2 +7E6545076619 +# SUBE cards keys (old) +4C5A766DFE3A +32C6768847F5 +F68930789631 +8B42B6D64B02 +B627A3CB13F8 +562A4FB8260B +88DDC24E1671 +91CB7802A559 +7A3E0F5B63FC +8CA2C9DC8292 +5CCC6D50EAAC +DE4F5AA9A7F3 +52D0145E1AF5 +C10F92A4E57E +7D6E7AF43C97 +DE1E7D5F6DF1 +F4CB751B031A +C54474936B59 +2A1F900D4533 +6303CDCBB233 +F115E91357B3 +BFE25035B0C8 +62FF943EB069 +7C82EF592001 +D5C172325DD3 +992B152E834A +CE75D7EADEAF +# Russian Podorozhnik card (Saint-Petersburg transport) +# may be combined with Troika +038B5F9B5A2A +04DC35277635 +0C420A20E056 +152FD0C420A7 +296FC317A513 +29C35FA068FB +31BEC3D9E510 +462225CD34CF +4B7CB25354D3 +5583698DF085 +578A9ADA41E3 +6F95887A4FD3 +7600E889ADF9 +86120E488ABF +8818A9C5D406 +8C90C70CFF4A +8E65B3AF7D22 +9764FEC3154A +9BA241DB3F56 +AD2BDC097023 +B0A2AAF3A1BA +B69D40D1A439 +C956C3B80DA3 +CA96A487DE0B +D0A4131FB290 +D27058C6E2C7 +E19504C39461 +FA1FBB3F0F1F +FF16014FEFC7 +# Food GEM +6686FADE5566 +# Samsung Data Systems (SDS) - Electronic Locks +# Gen 1 S10 KA/KB is FFFFFFFFFFFF, incompatible with Gen 2 locks +# SDS Gen 2 S10 KB +C22E04247D9A +# Data from Discord, French pool +# SDS Gen 2 S10 KA +9B7C25052FC3 +494446555455 +# Data from Discord, seems to be related to ASSA +427553754D47 +# Keys found on Edith Cowan University Smart Riders +9A677289564D +186C59E6AFC9 +DDDAA35A9749 +9D0D0A829F49 +# Mercator Pika Card, Slovenia +97D77FAE77D3 +5AF445D2B87A +# Vilniecio/JUDU kortele, Lithuania +# A +16901CB400BC +F0FE56621A42 +8C187E78EE9C +FE2A42E85CA8 +# B +6A6C80423226 +F4CE4AF888AE +307448829EBC +C2A0105EB028 +# Keys from Flipper Zero Community +# Last update: Aug 13, 2022 +# unknown if keys are diversified or static default +# Strelka Extension 5C83859F2224 66B504430416 70D1CF2C6843 @@ -1358,7 +1697,7 @@ C4D3911AD1B3 CAD7D4A6A996 DA898ACBB854 FEA1295774F9 -# MOSCOW PUBLIC TOILETS CARD +# Moscow Public Toilets Card 807119F81418 22C8BCD10AAA 0AAABA420191 @@ -1367,7 +1706,7 @@ DBF9F79AB7A2 34EDE51B4C22 C8BCD10AAABA BCD10AAABA42 -# MOSCOW SOCIAL CARD +# Moscow Social Card 2F87F74090D1 E53EAEFE478F CE2797E73070 @@ -1409,11 +1748,7 @@ F750C0095199 82DA4B93DB1C 9CF46DB5FD46 93EB64ACF43D -# KEYS FROM RFIDRESEARCHGROUP PROXMARK3 PROJECT -# HTTPS://GITHUB.COM/RFIDRESEARCHGROUP/PROXMARK3/BLOB/MASTER/CLIENT/DICTIONARIES/MFC_DEFAULT_KEYS.DIC -13B91C226E56 -5A7A52D5E20D -# IRON LOGIC +# Iron Logic RU A3A26EF4C6B0 2C3FEAAE99FC E85B73382E1F @@ -1431,10 +1766,354 @@ DEC0CEB0CE24 413BED2AE45B D6261A9A4B3F CB9D507CE56D -# MORE KEYS FROM THE PM3 REPO -# KEYS OF ARMENIAN UNDERGROUND TICKET +# Armenian Underground Ticket A0A1A2A8A4A5 -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_bmp_sorted.dic +# Badge Maker Leaked from https://github.com/UberGuidoZ +1A1B1C1D1E1F +1665FE2AE945 +158B51947A8E +E167EC67C7FF +D537320FF90E +5E56BFA9E2C9 +F81CED821B63 +C81584EF5EDF +9551F8F9259D +36E1765CE3E8 +509052C8E42E +776C9B03BE71 +C608E13ADD50 +BEE8B345B949 +ED0EC56EEFDD +9716D5241E28 +05D1FC14DC31 +3321FB75A356 +F22A78E29880 +EC211D12C98D +8CCA8F62A551 +B637E46AD674 +39605B3C8917 +3882719778A1 +9F27D36C4230 +DB32A6811327 +8AA8544A2207 +8C5819E780A4 +7549E90353A2 +2E52ABE0CE95 +E46210ED98AB +61D030C0D7A8 +18E20102821E +DA59354DFB88 +040047C12B75 +D10008074A6F +686E736F6E20 +446176696453 +6F6674776172 +6520446F7665 +# Apartment keyfobs (USA) (Corvette830) +E60F8387F0B9 +FFD46FF6C5EE +4F9661ED2E70 +576A798C9904 +1C5179C4A8A1 +16CA203B811B +11AC8C8F3AF2 +# The Westin Jakarta Indonesia (D4DB0D) +# Peppers Hotel Unknown location (D4D0D) +6E0DD4136B0A +141940E9B71B +3B1D3AAC866E +95E9EE4CCF8F +FEA6B332F04A +BE0EC5155806 +0500D6BFCC4F +FC5AC7678BE3 +F09BB8DD142D +B4B3FFEDBE0A +540E0D2D1D08 +# Schlage 9691T Keyfob (seasnaill) +7579B671051A +4F4553746B41 +# Vigik ScanBadge App (fr.badgevigik.scanbadge) +# Website https://badge-vigik.fr/ (Alex) +0000A2B3C86F +021200C20307 +021209197507 +1E34B127AF9C +303041534956 +4143532D494E +41454E521985 +43412D627400 +455249524345 +456666456666 +45B722C63319 +484585414354 +4D414C414741 +536563644C65 +57D27B730760 +593DD8FE167A +6472616E7265 +65626F726369 +680E95F3C287 +709BA7D4F920 +8829DAD9AF76 +92D0A0999CBA +948EE7CFC9DB +9EB7C8A6D4E3 +A22AE12C9013 +AFC984A3576E +# Vigik verified by quantum-x +# https://github.com/RfidResearchGroup/proxmark3/pull/1742#issuecomment-1206113976 +A00027000099 +A00016000028 +A00003000028 +A0000F000345 +A00001000030 +A00002000086 +A00002000036 +A00002000088 +A00000000058 +A00000000096 +A00000000008 +A00000043D79 +A00000000064 +A00025000030 +A00003000057 +# BH USA 2013 conference +012279BAD3E5 +# iGuard Simple (and reverse) keys +AAAAAAFFFFFF +FFFFFFAAAAAA +# Random Hotel A Key Sec 0 Blk 3 - KABA Lock (VideoMan) +3111A3A303EB +# Transport system Uruguay - STM +# Shared key - sec 0 blk 3 +D144BD193063 +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=45659#p45659 +3515AE068CAD +# Keys Catering +6A0D531DA1A7 +4BB29463DC29 +# Keys Swim +8627C10A7014 +453857395635 +# Unknown hotel system Sec 0 / A +353038383134 +# Brazil transport Sec 8 / A +50D4C54FCDF5 +# Bandai Namco Passport [fka Banapassport] / Sega Aime Card +# Dumped on the Flipper Devices Discord Server +6090D00632F5 +019761AA8082 +574343467632 +A99164400748 +62742819AD7C +CC5075E42BA1 +B9DF35A0814C +8AF9C718F23D +58CD5C3673CB +FC80E88EB88C +7A3CDAD7C023 +30424C029001 +024E4E44001F +ECBBFA57C6AD +4757698143BD +1D30972E6485 +F8526D1A8D6D +1300EC8C7E80 +F80A65A87FFA +DEB06ED4AF8E +4AD96BF28190 +000390014D41 +0800F9917CB0 +730050555253 +4146D4A956C4 +131157FBB126 +E69DD9015A43 +337237F254D5 +9A8389F32FBF +7B8FB4A7100B +C8382A233993 +7B304F2A12A6 +FC9418BF788B +# Data from "the more the marriott" mifare project (colonelborkmundus) +# aka The Horde +# These keys seem to be from Vingcard / Saflok system which means they are diversified +# and not static default keys. To verify this, the UID from such a card is needed. +# 20230125-01, Elite Member Marriott Rewards +43012BD9EB87 +# 20230125-02, Elite Member Marriott Rewards +3119A70628EB +# 20230125-03, Elite Member Marriott Rewards +23C9FDD9A366 +# 20230125-04, Elite Member Marriott Rewards +7B4DFC6D6525 +# 20230125-05, Elite Member Marriott Rewards +1330824CD356 +# 20230125-06, Elite Member Marriott Rewards +30AAD6A711EF +# 20230125-07, Fairfield Inn & Suites Marriott +7B3B589A5525 +# 20230125-08, Moxy Hotels +20C166C00ADB +# 20230125-09, Westin Hotels & Resorts +7D0A1C277C05 +2058580A941F +8C29F8320617 +# 20230125-10, Westin Hotels & Resorts +C40964215509 +D44CFC178460 +5697519A8F02 +# 20230125-12, AC Hotels Marriott +7B56B2B38725 +# 20230125-13, AC Hotels Marriott +8EA8EC3F2320 +# 20230125-14, Waldorf Astoria Chicago +011C6CF459E8 +# 20230125-24, Aria Resort & Casino +A18D9F4E75AF +# 20230125-25, Aria Resort & Casino +316B8FAA12EF +# 20230125-26, Residence Inn Mariott +3122AE5341EB +# 20230125-27, Residence Inn Mariott +F72CD208FDF9 +# 20230125-28, Marriott +035C70558D7B +# 20230125-29, Marriott +12AB4C37BB8B +# 20230125-30, Marriott +9966588CB9A0 +# 20230125-31, Sheraton +42FC522DE987 +# 20230125-32, The Industrialist +2158E314C3DF +# 20230125-39, The Ritz-Carlton Balharbour +30FB20D0EFEF +# 20230125-40, The Ritz-Carlton Balharbour +66A3B064CC4B +# 20230125-41, The Ritz-Carlton Balharbour +D18296CD9E6E +# 20230125-42, The Ritz-Carlton Balharbour +D20289CD9E6E +# 20230125-44, Graduate Hotels +209A2B910545 +C49DAE1C6049 +# 20230125-46, AmericInn +8AC04C1A4A25 +# 20230129-53, Marriott Bonvoy +6E029927600D +3E173F64C01C +C670A9AD6066 +# 20230413-69, Westin +487339FA02E0 +# 20230413-70, Marriott Bonvoy +DBD5CA4EE467 +A0B1F234006C +180DE12B700E +# 20230413-71, Westin +1352C68F7A56 +# 20230413-76, Ritz Carlton +318BD98C1CEF +# 20230413-77, Marriott +D23C1CB1216E +# 20230413-78, Caesars +A1D92F808CAF +# 20230413-79, The Cosmopolitan, Vegas +# 20230413-80, Aria +1153C319B4F8 +# 20230413-81, Aria +110C819BBEF8 +# 20230413-82, Aria +1332117E8756 +# 20230413-83, Kimpton +500AE915F50A +5032E362B484 +8B63AB712753 +# 20230413-85, Kimpton +06106E187106 +2E45C23DC541 +D9FF8BEE7550 +# 20230413-87, Marriott +42F7A186BF87 +# 20230413-88, Meritage Resort +D213B093B79A +# 20230413-89, Meritage Resort +216024C49EDF +# 20230413-90, Gaylord Palms +D201DBB6AB6E +# 20230413-91, Residence Inn +9F4AD875BB30 +# 20230413-92, Marriott +3352DB1E8777 +# 20230413-94, Marriott +09074A146605 +151F3E85EC46 +# Travelodge by Wyndham Berkeley +0000FFFFFFFF +4663ACD2FFFF +EDC317193709 +# Hotel Santa Cruz +75FAB77E2E5B +# saflok brand HOTEL key +32F093536677 +# A WaterFront Hotel in Oakland +3351916B5A77 +# Ballys (2018) +336E34CC2177 +# Random Hawaiian Hotel +A1670589B2AF +# SF Hotel (SoMa area) +2E0F00700000 +# Unknown PACS from Western Australia +CA80E51FA52B +A71E80EA35E1 +05597810D63D +# Hotel Key from Las Vegas +EA0CA627FD06 +80BB8436024C +5044068C5183 +# Key from Hotel M Montreal (probably diversified) +7E5E05866ED6 +661ABF99AFAD +# Key from evo Montreal (probably diversified) +1064BA5D6DF8 +# Hotel key +CE0F4F15E909 +D60DE9436219 +# ATM Area de Girona, spanish transport card +A01000000000 +A02000000000 +A03000000000 +A04000000000 +A05000000000 +A06000000000 +A07000000000 +A08000000000 +A09000000000 +A10000000000 +A11000000000 +A12000000000 +A13000000000 +A14000000000 +A15000000000 +B01000000000 +B02000000000 +B03000000000 +B04000000000 +B05000000000 +B06000000000 +B07000000000 +B08000000000 +B09000000000 +B10000000000 +B11000000000 +B12000000000 +B13000000000 +B14000000000 +B15000000000 +# +--------------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_bmp_sorted.dic | +# +--------------------------------------------------------------------------------------------------------+ 002DE0301481 004173272D18 0058A4884CA5 @@ -2435,7 +3114,9 @@ EE17C426D25E EE487A4C806E EE5931913A8D EED56840AEBA -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_icbmp_sorted.dic +# +----------------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_icbmp_sorted.dic | +# +----------------------------------------------------------------------------------------------------------+ 00383D96411D 005307DB7853 009A4C4C6C49 @@ -3436,276 +4117,59 @@ EE3029556CEB EE49610E6121 EEB704D69BCA EED69A391464 -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_mrzd_sorted.dic -010203040506 -013940233313 -022FE48B3072 -123456789ABC -123456ABCDEF -17505586EF02 -1795902DBAF9 -1A2B3C4D5E6F -1A982C7E459A -200306202033 -2011092119F1 -2012053082AD -37D4DCA92451 -40E5EA1EFC00 -435330666666 -46868F6D5677 -474249437569 -4D3A99C351DD -533CB6C723F6 -5554AAA96321 -587EE5F9350F -5A1B85FCE20A -5D293AFC8D7E -64A2EE93B12B -64E2283FCF5E -714C5C886E97 -833FBD3CFE51 -83BAB5ACAD62 -872B71F9D15A -8F9B229047AC -8FD0A4F256E9 -9AEDF9931EC1 -9B1DD7C030A1 -A0478CC39091 -A0A1A2A3A4A5 -A2B2C9D187FB -A4EF6C3BB692 -AABBCC660429 -AABBCCDDEEFF -ABCDEF123456 -B0699AD03D17 -B0B1B2B3B4B5 -BA28CFD15EE8 -BCFE01BCFE01 -C0C1C2C3C4C5 -CFC738403AB0 -D0D1D2D3D4D5 -D3F7D3F7D3F7 -DB5181C92CBE -DFED39FFBB76 -E1DD284379D4 -E96246531342 -ED3A7EFBFF56 -F83466888612 -F89C86B2A961 -FFFFAE82366C -FFFFD06F83E3 -# Unknown origin -2DEB57A3EA8F -32C1BB023F87 -70E3AD3F2D29 -202ECDCCC642 -3686192D813F -24501C422387 -2C7813A721C3 -FFE04BE3D995 -D28F090677A1 -DE2D83E2DCCC -A66A478712EA -643232ADB2D5 -C7F4A4478415 -95C013B70D99 -3C383889362A -3C6D9C4A90FA -51BEDBA005E5 -74BF7363F354 -53B09DB89111 -E98075318085 -2F904641D75F -7F60AEF68136 -F5C1B3F62FDA -3E6E5713BA10 -8B75A29D4AB2 -7E6545076619 -4C5A766DFE3A -32C6768847F5 -F68930789631 -8B42B6D64B02 -B627A3CB13F8 -562A4FB8260B -88DDC24E1671 -91CB7802A559 -7A3E0F5B63FC -8CA2C9DC8292 -5CCC6D50EAAC -DE4F5AA9A7F3 -52D0145E1AF5 -C10F92A4E57E -7D6E7AF43C97 -DE1E7D5F6DF1 -F4CB751B031A -C54474936B59 -2A1F900D4533 -6303CDCBB233 -F115E91357B3 -BFE25035B0C8 -62FF943EB069 -7C82EF592001 -D5C172325DD3 -992B152E834A -CE75D7EADEAF -038B5F9B5A2A -04DC35277635 -0C420A20E056 -152FD0C420A7 -296FC317A513 -29C35FA068FB -31BEC3D9E510 -462225CD34CF -4B7CB25354D3 -5583698DF085 -578A9ADA41E3 -6F95887A4FD3 -7600E889ADF9 -86120E488ABF -8818A9C5D406 -8C90C70CFF4A -8E65B3AF7D22 -9764FEC3154A -9BA241DB3F56 -AD2BDC097023 -B0A2AAF3A1BA -B69D40D1A439 -C956C3B80DA3 -CA96A487DE0B -D0A4131FB290 -D27058C6E2C7 -E19504C39461 -FA1FBB3F0F1F -FF16014FEFC7 -# Cracked by UberGuidoZ -# https://github.com/UberGuidoZ -# BadgeMaker Leaked -1A1B1C1D1E1F -1665FE2AE945 -158B51947A8E -5E56BFA9E2C9 -F81CED821B63 -C81584EF5EDF -9551F8F9259D -509052C8E42E -776C9B03BE71 -BEE8B345B949 -05D1FC14DC31 -3321FB75A356 -F22A78E29880 -EC211D12C98D -8CCA8F62A551 -B637E46AD674 -39605B3C8917 -3882719778A1 -9F27D36C4230 -DB32A6811327 -8AA8544A2207 -8C5819E780A4 -7549E90353A2 -E46210ED98AB -18E20102821E -DA59354DFB88 -040047C12B75 -D10008074A6F -686E736F6E20 -446176696453 -6F6674776172 -6520446F7665 -# Apartment keyfobs in USA from Corvette830 -E60F8387F0B9 -FFD46FF6C5EE -4F9661ED2E70 -576A798C9904 -1C5179C4A8A1 -16CA203B811B -11AC8C8F3AF2 -# The Westin Jakarta Indonesia from D4DB0D -# Peppers Hotel Unknown location from D4DB0D -6E0DD4136B0A -141940E9B71B -3B1D3AAC866E -95E9EE4CCF8F -0000014B5C31 -FEA6B332F04A -BE0EC5155806 -0500D6BFCC4F -FC5AC7678BE3 -F09BB8DD142D -B4B3FFEDBE0A -540E0D2D1D08 -# Schlage 9691T Keyfob -7579B671051A -4F4553746B41 -# FOOD REPUBLIC -30C1DC9DD040 -A9B9C1D0E3F1 -# iGuard Simple (and reverse) keys -AAAAAAFFFFFF -FFFFFFAAAAAA -# Vigik verified by quantum-x -# https://github.com/RfidResearchGroup/proxmark3/pull/1742#issuecomment-1206113976 -A00027000099 -A00016000028 -A00003000028 -A0000F000345 -A00001000030 -A00002000086 -A00002000036 -A00002000088 -A00000000058 -A00000000096 -A00000000008 -A00000043D79 -A00000000064 -A00025000030 -A00003000057 -# BH USA 2013 conference -012279BAD3E5 -# Vigik ScanBadge App (fr.badgevigik.scanbadge) -# Website https://badge-vigik.fr/ - By Alex` -0000A2B3C86F -021200C20307 -021209197507 -1E34B127AF9C -303041534956 -4143532D494E -41454E521985 -43412D627400 -455249524345 -456666456666 -45B722C63319 -484585414354 -4D414C414741 -536563644C65 -57D27B730760 -593DD8FE167A -6472616E7265 -65626F726369 -680E95F3C287 -709BA7D4F920 -8829DAD9AF76 -92D0A0999CBA -948EE7CFC9DB -9EB7C8A6D4E3 -A22AE12C9013 -AFC984A3576E +# +-----------------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/ikarus23/MifareClassicTool/blob/master/Mifare%20Classic%20Tool/app/src/main/assets/key-files/extended-std.keys | +# +-----------------------------------------------------------------------------------------------------------------------------------+ +# Wojo coworking space, Fance +FF75AFDA5A3C +558AAD64EB5B +518108E061E2 +FCDDF7767C10 +A6B3F6C8F1D4 +B1C4A8F7F6E3 +# Key from some random card +001122334455 +# Key from hotel in Spain +6CA761AB6CA7 +# +----------------------------------------------------------------------------------------------+ +# | https://github.com/UberGuidoZ/Flipper/blob/main/NFC/mf_classic_dict/mf_classic_dict_user.nfc | +# +----------------------------------------------------------------------------------------------+ # Spackular A/B # data from http://www.proxmark.org/forum/viewtopic.php?pid=45100#p45100 7CB033257498 1153AABAFF6C -# iGuard Simple and Reverse Keys -D537320FF90E -36E1765CE3E8 -C608E13ADD50 -ED0EC56EEFDD -9716D5241E28 -2E52ABE0CE95 -61D030C0D7A8 -# BadgeMaker Leaked from https://github.com/UberGuidoZ -E167EC67C7FF -# Schlage 9691T Keyfob from seasnaill Added by VideoMan. -3111A3A303EB +# Random Hotel A key sec 0 blk 3 - Hoist Group lock +A2CA48CA4C05 +# Chelsea Harbour Hotel London by Cazagen +485242F22BE0 +# 8668/RevKillj0y +164EE10EFFFF +193DFE0FA18E +3D6F823FFFFF +48C8852D15F9 +4E4F584D2105 +7213B13D02E0 +7ADD3D735725 +7F796F60FFFF +8AC04C1A4E15 +9089B668FFFF +AEF617B3D004 +AEF617B3D040 +C1F6C7B55F5E +# Metro card Sec 001 Blk 007 key B +EDA4BF3E7B04 +# +--------------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +--------------------------------------------------------------------------------------------------------------------------------+ +# Volgograd (Russia) Volna transport cards keys +2B787A063D5D +D37C8F1793F7 +# +----------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +----------------------------------------------------------------------------------------------------------------------------+ +# FOOD REPUBLIC +30C1DC9DD040 +A9B9C1D0E3F1 # Transport cards E954024EE754 0CD464CDC100 @@ -3723,7 +4187,6 @@ F18D91EE3033 0E726E11CFCC 1D14130D1A0B 201106141030 -D144BD193063 01E2C14F1B18 0380293A9E6D 08A55BC96DC1 @@ -3764,104 +4227,120 @@ E10F0E7A8DD5 F833E24C3F1C FA8CA10C7D59 FE98F38F3EE2 -#---------------------------------------------------- -# Added by colonelborkmundus, cleaned by scaff.walker -# "the more, the marriott" Mifare project -# 1k GRADUATE HOTEL -C49DAE1C6049 -209A2B910545 -# 1k WESTIN -8C29F8320617 -5697519A8F02 -7D0A1C277C05 -2058580A941F -C40964215509 -D44CFC178460 -# 1k MARRIOT -7B4DFC6D6525 -23C9FDD9A366 -3119A70628EB -30AAD6A711EF -1330824CD356 -43012BD9EB87 -035C70558D7B -9966588CB9A0 -12AB4C37BB8B -# 1k AC HOTELS MARRIOT -8EA8EC3F2320 -7B56B2B38725 -# 1k THE RITZ-CARTLON -30FB20D0EFEF -D20289CD9E6E -66A3B064CC4B -D18296CD9E6E # 1k UNKNOWN 722538817225 -# 1k ARIA RESORT & CASINO -316B8FAA12EF -A18D9F4E75AF # 1k FAIRFIELD INN & SUITES MARRIOT 7AEB989A5525 -7B3B589A5525 215E9DED9DDF 334E91BE3377 310308EC52EF -# 1k RESIDENCE INN MARRIOT -F72CD208FDF9 -# 1k SHERATON -42FC522DE987 # 1k millenium hotels 132F641C948B # 1k MOXY HOTELS -20C166C00ADB 9EE3896C4530 -# 1k RESIDENCE INN MARRIOT -3122AE5341EB -# 1k AMERICINN -8AC04C1A4A25 -# 1k THE INDUSTRIALIST -2158E314C3DF -# 1k WALDORF ASTORIA -011C6CF459E8 # 1k HAWAII HOTEL 2CAD8A83DF28 555D8BBC2D3E 78DF1176C8FD ADC169F922CB -# Volgograd (Russia) Volna transport cards keys -2B787A063D5D -D37C8F1793F7 -# Bandai Namco Passport [fka Banapassport] / Sega Aime Card -6090D00632F5 -019761AA8082 -574343467632 -A99164400748 -62742819AD7C -CC5075E42BA1 -B9DF35A0814C -8AF9C718F23D -58CD5C3673CB -FC80E88EB88C -7A3CDAD7C023 -30424C029001 -024E4E44001F -ECBBFA57C6AD -4757698143BD -1D30972E6485 -F8526D1A8D6D -1300EC8C7E80 -F80A65A87FFA -DEB06ED4AF8E -4AD96BF28190 -000390014D41 -0800F9917CB0 -730050555253 -4146D4A956C4 -131157FBB126 -E69DD9015A43 -337237F254D5 -9A8389F32FBF -7B8FB4A7100B -C8382A233993 -7B304F2A12A6 -FC9418BF788B +# +------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/Flipper-XFW/Xtreme-Firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +------------------------------------------------------------------------------------------------------------------------+ +############################################## +# STB - keys from Bucharest Public Transport +# Found with FlipperNestedRecovery by Z3r0L1nk +330075000850 +2FA9B556A4F6 +91B1B62402D5 +9AE05868233F +6D2BF79566A8 +60D53F070572 +09F4EC8D7A66 +D017A84BB582 +1719EB5DAC66 +96AECCC0F7EB +A64536FAC799 +7BF0BE85080F +E9B376925A00 +44B61F116125 +E861FDE1B59F +5CD02DAD8ADE +5FA28B8E8BA4 +C497C3BE8273 +B1EEAA640EF6 +1E13EFF32CE2 +D6818C29ED9B +050BF33DC217 +41CD3CD99DD5 +02DB253DC0C7 +25467EB0212F +DF7C4EC20B50 +5703815494EF +9F14D35BAC08 +F697E87E759D +D9762D114AE5 +AC935925A876 +5AD3FC074A4C +19BA6776233F +EEF144866688 +C34FAA1931CA +8A0DFD9B7AEA +D3AEB15D410B +251BDBF1C71D +286A8893AC6F +6A86C1895A21 +D7142E0F6D0D +B0A3212D47A5 +1581C317B073 +8EF0AA6432FA +AD00EFD353E4 +439FB891279F +141DF3B1C017 +58DBC850A4D5 +14CD299DC0C7 +B48D7E4E508F +32774E46C64F +61152534ACEF +280FD37AD407 +C789E4568B99 +122F595302AA +84A3FD4BA0C6 +B0D58BF147B7 +15B35D0BF715 +############################################## +# Metrorex - keys from Subway Bucharest Public Transport +# Found with FlipperNestedRecovery by Z3r0L1nk +7246FCE86427 +1415FFFED68D +8C524B535E1D +0FC4B1D2EBBA +280713CBA260 +EDC9CC9109A2 +DCD003CF0EA3 +DD30A13519C3 +8A35039F6CD6 +538BF58687EB +E3007FA4F781 +F654D6C7004F +7C20975C6EC9 +C992F85B2DDD +156EED7C5F9D +16785FD65BA7 +A2D5D7469472 +585462E190F2 +FA4D2B3BAFEA +251780F9FBE6 +EA4987F8D096 +48B390984150 +EA19E58DD046 +1C000EB0752F +96227EDADBCF +3F41891454EE +7EDAE7923287 +11DDA4862A1C +############################################## +# Bubbleland Play Card - keys from Bubble Land Arcade +# Found with FlipperNestedRecovery by CaitSith2 +168168168168 +861861861861 +686B35333376 From 357df80642513d870fbfe65263d132f1cbe40bde Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Feb 2024 14:51:23 +0300 Subject: [PATCH 34/36] apply fix for msteams on macos by cpressland https://github.com/Flipper-XFW/Xtreme-Firmware/pull/545/files --- applications/system/hid_app/views/hid_ptt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 1d71490a2..3b7031a36 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -276,7 +276,7 @@ static void hid_ptt_stop_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) { // teams static void hid_ptt_start_ptt_macos_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_start_ptt_linux_teams(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_SPACEBAR); From 37708b0e79d60ecf5ea3e79331450728e89b0918 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 10 Feb 2024 23:40:58 +0900 Subject: [PATCH 35/36] new parsers minor fixes --- applications/main/nfc/plugins/supported_cards/hi.c | 4 ++-- applications/main/nfc/plugins/supported_cards/microel.c | 4 ++-- applications/main/nfc/plugins/supported_cards/mizip.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c index 6807ab00c..1d390196c 100644 --- a/applications/main/nfc/plugins/supported_cards/hi.c +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -153,14 +153,14 @@ static bool hi_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/microel.c b/applications/main/nfc/plugins/supported_cards/microel.c index 899ad2a7e..12c196d11 100644 --- a/applications/main/nfc/plugins/supported_cards/microel.c +++ b/applications/main/nfc/plugins/supported_cards/microel.c @@ -143,14 +143,14 @@ static bool microel_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index b76c381e9..4fd1bd5e0 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -166,14 +166,14 @@ static bool mizip_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); From 8a86571068f8d82093bbfe71308efcf8eabee77a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Feb 2024 17:55:19 +0300 Subject: [PATCH 36/36] fit full name on display --- applications/main/nfc/scenes/nfc_scene_set_type.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index b5102f801..e33660080 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -32,20 +32,10 @@ void nfc_scene_set_type_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, instance); - FuriString* str = furi_string_alloc(); for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { - furi_string_cat_str(str, nfc_data_generator_get_name(i)); - furi_string_replace_str(str, "Mifare", "MIFARE"); - - submenu_add_item( - submenu, - furi_string_get_cstr(str), - i, - nfc_protocol_support_common_submenu_callback, - instance); - furi_string_reset(str); + const char* name = nfc_data_generator_get_name(i); + submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); } - furi_string_free(str); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); }