1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-13 13:09:49 +04:00

Merge branch 'dev' into release

This commit is contained in:
MX
2024-01-31 04:06:23 +03:00
227 changed files with 10111 additions and 1853 deletions

View File

@@ -1,5 +1,5 @@
diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c
index 5769ced..c5d3088 100644
index 9baa738..91ad7c1 100644
--- a/applications/services/notification/notification_app.c
+++ b/applications/services/notification/notification_app.c
@@ -9,6 +9,7 @@
@@ -19,7 +19,7 @@ index 5769ced..c5d3088 100644
}
diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c
index 1955012..19d953d 100644
index 2a1d988..dda86f3 100644
--- a/applications/settings/notification_settings/notification_settings_app.c
+++ b/applications/settings/notification_settings/notification_settings_app.c
@@ -3,6 +3,7 @@
@@ -30,16 +30,16 @@ index 1955012..19d953d 100644
#define MAX_NOTIFICATION_SETTINGS 4
@@ -20,6 +21,8 @@ static const NotificationSequence sequence_note_c = {
NULL,
};
@@ -13,6 +14,8 @@ typedef struct {
VariableItemList* variable_item_list;
} NotificationAppSettings;
+static VariableItem* temp_item;
+
#define CONTRAST_COUNT 11
const char* const contrast_text[CONTRAST_COUNT] = {
"-5",
@@ -156,6 +159,59 @@ static void vibro_changed(VariableItem* item) {
static const NotificationSequence sequence_note_c = {
&message_note_c5,
&message_delay_100,
@@ -168,6 +171,59 @@ static void vibro_changed(VariableItem* item) {
notification_message(app->notification, &sequence_single_vibro);
}
@@ -99,7 +99,7 @@ index 1955012..19d953d 100644
static uint32_t notification_app_settings_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
@@ -180,8 +236,40 @@ static NotificationAppSettings* alloc_settings() {
@@ -192,8 +248,40 @@ static NotificationAppSettings* alloc_settings() {
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, contrast_text[value_index]);
@@ -462,54 +462,6 @@ index 0000000..68dacda
+ */
+const char* rgb_backlight_get_color_text(uint8_t index);
\ No newline at end of file
diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c
index 83e1603..45798ca 100644
--- a/targets/f7/furi_hal/furi_hal_light.c
+++ b/targets/f7/furi_hal/furi_hal_light.c
@@ -3,6 +3,7 @@
#include <furi_hal_light.h>
#include <lp5562.h>
#include <stdint.h>
+#include <applications/settings/notification_settings/rgb_backlight.h>
#define LED_CURRENT_RED 50
#define LED_CURRENT_GREEN 50
@@ -31,22 +32,21 @@ void furi_hal_light_init() {
}
void furi_hal_light_set(Light light, uint8_t value) {
- furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
- if(light & LightRed) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value);
- }
- if(light & LightGreen) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value);
- }
- if(light & LightBlue) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
- }
if(light & LightBacklight) {
- uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite);
- lp5562_execute_ramp(
- &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100);
+ rgb_backlight_update(value, false);
+ } else {
+ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
+ if(light & LightRed) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value);
+ }
+ if(light & LightGreen) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value);
+ }
+ if(light & LightBlue) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
+ }
+ furi_hal_i2c_release(&furi_hal_i2c_handle_power);
}
- furi_hal_i2c_release(&furi_hal_i2c_handle_power);
}
void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) {
diff --git a/lib/drivers/SK6805.c b/lib/drivers/SK6805.c
new file mode 100644
index 0000000..572e1df
@@ -675,3 +627,51 @@ index 0000000..7c58956
+
+#endif /* SK6805_H_ */
\ No newline at end of file
diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c
index 83e1603..45798ca 100644
--- a/targets/f7/furi_hal/furi_hal_light.c
+++ b/targets/f7/furi_hal/furi_hal_light.c
@@ -3,6 +3,7 @@
#include <furi_hal_light.h>
#include <lp5562.h>
#include <stdint.h>
+#include <applications/settings/notification_settings/rgb_backlight.h>
#define LED_CURRENT_RED 50
#define LED_CURRENT_GREEN 50
@@ -31,22 +32,21 @@ void furi_hal_light_init() {
}
void furi_hal_light_set(Light light, uint8_t value) {
- furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
- if(light & LightRed) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value);
- }
- if(light & LightGreen) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value);
- }
- if(light & LightBlue) {
- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
- }
if(light & LightBacklight) {
- uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite);
- lp5562_execute_ramp(
- &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100);
+ rgb_backlight_update(value, false);
+ } else {
+ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
+ if(light & LightRed) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value);
+ }
+ if(light & LightGreen) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value);
+ }
+ if(light & LightBlue) {
+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
+ }
+ furi_hal_i2c_release(&furi_hal_i2c_handle_power);
}
- furi_hal_i2c_release(&furi_hal_i2c_handle_power);
}
void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) {

View File

@@ -375,7 +375,7 @@ trigger:
- tag
node:
typ: haupt
typ: dev1
---
kind: pipeline
@@ -678,4 +678,4 @@ trigger:
- push
node:
typ: haupt
typ: dev1

View File

@@ -1,11 +1,47 @@
## New changes
* NFC: Various NFC Mifare classic Read fixes (was caused by wrong logic in parsers) (mifare classic 4k, and others) (by @Leptopt1los)
* Apps: Fixed Unitemp and ESP32 Camera suite
* NFC: **EMV parser** added (by @Leptopt1los and @wosk | PR #700)
* NFC: Metromoney parser balance fix (by @Leptopt1los | PR #699)
* NFC/LFRFID: Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los)
* Archive: Fix two filebrowser bugs
* SubGHz: **Programming mode for Dea Mio** (right arrow button)
* SubGHz: **Keeloq fix emulation for multiple systems and extend add manually support** for 2 of them (Dea Mio, Genius Bravo, GSN, Normstahl)
* SubGHz: Fixed hopper state when entering Read via Freq analyzer
* SubGHz: Raw erase fixes (by @Willy-JL)
* SubGHz: Subghz save files with receive time (by @Willy-JL)
* NFC: Fix NFC V dumps with v3 (pre refactor saves) crashing at info page
* NFC: Zolotaya Korona Online parser added (by @Leptopt1los)
* NFC: Add NFC **NDEF parser** (by @Willy-JL)
* LF RFID: **Write T5577 with random and custom password** added (clear password via Extra actions) (by @Leptopt1los)
* SubGHz: Update honeywell protocol (by @Willy-JL)
* System: More contrast values for replacement displays (up to +8 or -8)
* USB/BLE HID: Add macOS Music app volume control
* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
* OFW PR 3384: NFC: Display unread Mifare Classic bytes as question marks - by TollyH
* OFW PR 3396: NFC: **fix application opening from browser** - by RebornedBrain (+ fix for leftover issues)
* OFW PR 3382: NFC UI refactor - by RebornedBrain
* OFW PR 3391: Rework more info scene for Ultralight cards - by RebornedBrain
* OFW PR 3401: it-IT-mac layout - by nminaylov
* OFW: Fix expansion protocol crash when fed lots of garbage
* OFW: 0.98.0-rc various fixes
* OFW: RFID CLI: better usage
* OFW: **Mf DESFire fixes**
* OFW: NFC UI refactor
* OFW: **Expansion module protocol** (+ expansion settings read and store in ram by @Willy-JL)
* OFW: Bugfix: Strip last parity bit from decoded FDX-B data
* OFW: FuriHal: interrupt priorities and documentation
* OFW: FuriHal: **UART refactoring**
* OFW: SubGhz: add `subghz tx_from_file` CLI cmd, major TX flow refactoring, various improvements and bug fixes
* OFW: Furi_hal_rtc: new function
* OFW: NFC UI refactor
* OFW: assets: checking limits on image size; ufbt: cdb target
* OFW: NFC: system dict skip when user dict is skipped fix (replaces our fix)
* OFW: FuriHal: fix start duration furi_hal_subghz_async_tx
* OFW: NFC: parsers minor cleanup
* OFW: NFC Ntag success write freeze when not saved card
* OFW: ufbt: fixed generated project paths on Windows
<br><br>
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
- EMV simple data parser was removed with protocol with refactoring (OFW)
- Option to unlock Slix-L (NFC V) with preset or custom password was removed with refactoring (OFW)
- NFC CLI was removed with refactoring (OFW)
- Current list of affected apps: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors

View File

@@ -79,9 +79,10 @@
- **NFC/RFID/iButton**
* LFRFID/iButton Fuzzer plugins
* Extra Mifare Classic keys
* EMV Protocol + Public data parser (by @Leptopt1los and @wosk)
* NFC/LFRFID - Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los)
* NFC `Add manually` -> Mifare Classic with custom UID
* NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards)
* Picopass/iClass plugin (now with emulation support!) included in releases
* NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) (by @Leptopt1los and @assasinfil)
- **Quality of life & other features**
- Customizable Flipper name **Update! Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL)
- Text Input UI element -> Cursor feature (by @Willy-JL)
@@ -127,10 +128,11 @@ Encoders made by @assasinfil & @xMasterX:
The majority of this project is developed and maintained by me, @xMasterX.
I'm unemployed, and the only income I receive is from your donations.
Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community.
- @Leptopt1los - NFC, RFID, IR Assets (only ACs), Plugins, and many other things
- @gid9798 - SubGHz, Plugins, many other things
- @assasinfil - SubGHz protocols, NFC parsers (working with @Leptopt1los)
- @assasinfil - SubGHz protocols, NFC parsers
- @Svaarich - UI design and animations
- @amec0e & @Leptopt1los (only ACs) - Infrared assets
- @amec0e - Infrared assets
- Community moderators in Telegram, Discord, and Reddit
- And of course our GitHub community. Your PRs are a very important part of this firmware and open-source development.

View File

@@ -369,7 +369,7 @@ vscode_dist = distenv.Install(
)
distenv.Precious(vscode_dist)
distenv.NoClean(vscode_dist)
distenv.Alias("vscode_dist", vscode_dist)
distenv.Alias("vscode_dist", (vscode_dist, firmware_env["FW_CDB"]))
# Configure shell with build tools
distenv.PhonyTarget(

View File

@@ -0,0 +1,12 @@
App(
appid="expansion_test",
name="Expansion Module Test",
apptype=FlipperAppType.DEBUG,
entry_point="expansion_test_app",
requires=["expansion_start"],
fap_libs=["assets"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
fap_file_assets="assets",
)

View File

@@ -0,0 +1,9 @@
"Did you ever hear the tragedy of Darth Plagueis the Wise?"
"No."
"I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis... was a Dark Lord of the Sith so powerful and so wise, he could use the Force to influence the midi-chlorians... to create... life. He had such a knowledge of the dark side, he could even keep the ones he cared about... from dying."
"He could actually... save people from death?"
"The dark side of the Force is a pathway to many abilities... some consider to be unnatural."
"Wh What happened to him?"
"He became so powerful, the only thing he was afraid of was... losing his power. Which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew. Then his apprentice killed him in his sleep. It's ironic. He could save others from death, but not himself."
"Is it possible to learn this power?"
"Not from a Jedi."

View File

@@ -0,0 +1,457 @@
/**
* @file expansion_test.c
* @brief Expansion module support testing application.
*
* Before running, connect pins using the following scheme:
* 13 -> 16 (USART TX to LPUART RX)
* 14 -> 15 (USART RX to LPUART TX)
*
* What this application does:
*
* - Enables module support and emulates the module on a single device
* (hence the above connection),
* - Connects to the expansion module service, sets baud rate,
* - Starts the RPC session,
* - Creates a directory at `/ext/ExpansionTest` and writes a file
* named `test.txt` under it,
* - Plays an audiovisual alert (sound and blinking display),
* - Waits 10 cycles of idle loop,
* - Stops the RPC session,
* - Waits another 10 cycles of idle loop,
* - Exits (plays a sound if any of the above steps failed).
*/
#include <furi.h>
#include <furi_hal_resources.h>
#include <furi_hal_serial.h>
#include <furi_hal_serial_control.h>
#include <pb.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <flipper.pb.h>
#include <storage/storage.h>
#include <expansion/expansion.h>
#include <notification/notification_messages.h>
#include <expansion/expansion_protocol.h>
#define TAG "ExpansionTest"
#define TEST_DIR_PATH EXT_PATH(TAG)
#define TEST_FILE_NAME "test.txt"
#define TEST_FILE_PATH EXT_PATH(TAG "/" TEST_FILE_NAME)
#define HOST_SERIAL_ID (FuriHalSerialIdLpuart)
#define MODULE_SERIAL_ID (FuriHalSerialIdUsart)
#define RECEIVE_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum))
typedef enum {
ExpansionTestAppFlagData = 1U << 0,
ExpansionTestAppFlagExit = 1U << 1,
} ExpansionTestAppFlag;
#define EXPANSION_TEST_APP_ALL_FLAGS (ExpansionTestAppFlagData | ExpansionTestAppFlagExit)
typedef struct {
FuriThreadId thread_id;
Expansion* expansion;
FuriHalSerialHandle* handle;
FuriStreamBuffer* buf;
ExpansionFrame frame;
PB_Main msg;
Storage* storage;
} ExpansionTestApp;
static void expansion_test_app_serial_rx_callback(
FuriHalSerialHandle* handle,
FuriHalSerialRxEvent event,
void* context) {
furi_assert(handle);
furi_assert(context);
ExpansionTestApp* app = context;
if(event == FuriHalSerialRxEventData) {
const uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(app->buf, &data, sizeof(data), 0);
furi_thread_flags_set(app->thread_id, ExpansionTestAppFlagData);
}
}
static ExpansionTestApp* expansion_test_app_alloc() {
ExpansionTestApp* instance = malloc(sizeof(ExpansionTestApp));
instance->buf = furi_stream_buffer_alloc(RECEIVE_BUFFER_SIZE, 1);
return instance;
}
static void expansion_test_app_free(ExpansionTestApp* instance) {
furi_stream_buffer_free(instance->buf);
free(instance);
}
static void expansion_test_app_start(ExpansionTestApp* instance) {
instance->thread_id = furi_thread_get_current_id();
instance->expansion = furi_record_open(RECORD_EXPANSION);
instance->handle = furi_hal_serial_control_acquire(MODULE_SERIAL_ID);
furi_check(instance->handle);
// Configure the serial port
furi_hal_serial_init(instance->handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE);
// Start waiting for the initial pulse
expansion_set_listen_serial(instance->expansion, HOST_SERIAL_ID);
furi_hal_serial_async_rx_start(
instance->handle, expansion_test_app_serial_rx_callback, instance, false);
}
static void expansion_test_app_stop(ExpansionTestApp* instance) {
// Disable expansion module support
expansion_disable(instance->expansion);
// Give back the module handle
furi_hal_serial_control_release(instance->handle);
// Restore expansion user settings
expansion_enable(instance->expansion);
furi_record_close(RECORD_EXPANSION);
}
static inline bool expansion_test_app_is_success_response(const ExpansionFrame* response) {
return response->header.type == ExpansionFrameTypeStatus &&
response->content.status.error == ExpansionFrameErrorNone;
}
static inline bool expansion_test_app_is_success_rpc_message(const PB_Main* message) {
return (message->command_status == PB_CommandStatus_OK ||
message->command_status == PB_CommandStatus_ERROR_STORAGE_EXIST) &&
(message->which_content == PB_Main_empty_tag);
}
static size_t expansion_test_app_receive_callback(uint8_t* data, size_t data_size, void* context) {
ExpansionTestApp* instance = context;
size_t received_size = 0;
while(true) {
received_size += furi_stream_buffer_receive(
instance->buf, data + received_size, data_size - received_size, 0);
if(received_size == data_size) break;
const uint32_t flags = furi_thread_flags_wait(
EXPANSION_TEST_APP_ALL_FLAGS, FuriFlagWaitAny, EXPANSION_PROTOCOL_TIMEOUT_MS);
// Exit on any error
if(flags & FuriFlagError) break;
}
return received_size;
}
static size_t
expansion_test_app_send_callback(const uint8_t* data, size_t data_size, void* context) {
ExpansionTestApp* instance = context;
furi_hal_serial_tx(instance->handle, data, data_size);
furi_hal_serial_tx_wait_complete(instance->handle);
return data_size;
}
static bool expansion_test_app_receive_frame(ExpansionTestApp* instance, ExpansionFrame* frame) {
return expansion_protocol_decode(frame, expansion_test_app_receive_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool
expansion_test_app_send_status_response(ExpansionTestApp* instance, ExpansionFrameError error) {
ExpansionFrame frame = {
.header.type = ExpansionFrameTypeStatus,
.content.status.error = error,
};
return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool expansion_test_app_send_heartbeat(ExpansionTestApp* instance) {
ExpansionFrame frame = {
.header.type = ExpansionFrameTypeHeartbeat,
.content.heartbeat = {},
};
return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool
expansion_test_app_send_baud_rate_request(ExpansionTestApp* instance, uint32_t baud_rate) {
ExpansionFrame frame = {
.header.type = ExpansionFrameTypeBaudRate,
.content.baud_rate.baud = baud_rate,
};
return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool expansion_test_app_send_control_request(
ExpansionTestApp* instance,
ExpansionFrameControlCommand command) {
ExpansionFrame frame = {
.header.type = ExpansionFrameTypeControl,
.content.control.command = command,
};
return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool expansion_test_app_send_data_request(
ExpansionTestApp* 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_protocol_encode(&frame, expansion_test_app_send_callback, instance) ==
ExpansionProtocolStatusOk;
}
static bool expansion_test_app_rpc_encode_callback(
pb_ostream_t* stream,
const pb_byte_t* data,
size_t data_size) {
ExpansionTestApp* instance = stream->state;
size_t size_sent = 0;
while(size_sent < data_size) {
const size_t current_size = MIN(data_size - size_sent, EXPANSION_PROTOCOL_MAX_DATA_SIZE);
if(!expansion_test_app_send_data_request(instance, data + size_sent, current_size)) break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_is_success_response(&instance->frame)) break;
size_sent += current_size;
}
return size_sent == data_size;
}
static bool expansion_test_app_send_rpc_request(ExpansionTestApp* instance, PB_Main* message) {
pb_ostream_t stream = {
.callback = expansion_test_app_rpc_encode_callback,
.state = instance,
.max_size = SIZE_MAX,
.bytes_written = 0,
.errmsg = NULL,
};
const bool success = pb_encode_ex(&stream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
pb_release(&PB_Main_msg, message);
return success;
}
static bool expansion_test_app_receive_rpc_request(ExpansionTestApp* instance, PB_Main* message) {
bool success = false;
do {
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_send_status_response(instance, ExpansionFrameErrorNone)) break;
if(instance->frame.header.type != ExpansionFrameTypeData) break;
pb_istream_t stream = pb_istream_from_buffer(
instance->frame.content.data.bytes, instance->frame.content.data.size);
if(!pb_decode_ex(&stream, &PB_Main_msg, message, PB_DECODE_DELIMITED)) break;
success = true;
} while(false);
return success;
}
static bool expansion_test_app_send_presence(ExpansionTestApp* instance) {
// Send pulses to emulate module insertion
const uint8_t init = 0xAA;
furi_hal_serial_tx(instance->handle, &init, sizeof(init));
furi_hal_serial_tx_wait_complete(instance->handle);
return true;
}
static bool expansion_test_app_wait_ready(ExpansionTestApp* instance) {
bool success = false;
do {
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break;
success = true;
} while(false);
return success;
}
static bool expansion_test_app_handshake(ExpansionTestApp* instance) {
bool success = false;
do {
if(!expansion_test_app_send_baud_rate_request(instance, 230400)) break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_is_success_response(&instance->frame)) break;
furi_hal_serial_set_br(instance->handle, 230400);
furi_delay_ms(EXPANSION_PROTOCOL_BAUD_CHANGE_DT_MS);
success = true;
} while(false);
return success;
}
static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) {
bool success = false;
do {
if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStartRpc))
break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_is_success_response(&instance->frame)) break;
success = true;
} while(false);
return success;
}
static bool expansion_test_app_rpc_mkdir(ExpansionTestApp* instance) {
bool success = false;
instance->msg.command_id++;
instance->msg.command_status = PB_CommandStatus_OK;
instance->msg.which_content = PB_Main_storage_mkdir_request_tag;
instance->msg.has_next = false;
instance->msg.content.storage_mkdir_request.path = TEST_DIR_PATH;
do {
if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break;
success = true;
} while(false);
return success;
}
static bool expansion_test_app_rpc_write(ExpansionTestApp* instance) {
bool success = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
do {
if(!storage_file_open(file, APP_ASSETS_PATH(TEST_FILE_NAME), FSAM_READ, FSOM_OPEN_EXISTING))
break;
const uint64_t file_size = storage_file_size(file);
instance->msg.command_id++;
instance->msg.command_status = PB_CommandStatus_OK;
instance->msg.which_content = PB_Main_storage_write_request_tag;
instance->msg.has_next = false;
instance->msg.content.storage_write_request.path = TEST_FILE_PATH;
instance->msg.content.storage_write_request.has_file = true;
instance->msg.content.storage_write_request.file.data =
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(file_size));
instance->msg.content.storage_write_request.file.data->size = file_size;
const size_t bytes_read = storage_file_read(
file, instance->msg.content.storage_write_request.file.data->bytes, file_size);
if(bytes_read != file_size) {
pb_release(&PB_Main_msg, &instance->msg);
break;
}
if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break;
success = true;
} while(false);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return success;
}
static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) {
bool success = false;
instance->msg.command_id++;
instance->msg.command_status = PB_CommandStatus_OK;
instance->msg.which_content = PB_Main_system_play_audiovisual_alert_request_tag;
instance->msg.has_next = false;
do {
if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break;
if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break;
if(instance->msg.which_content != PB_Main_empty_tag) break;
if(instance->msg.command_status != PB_CommandStatus_OK) break;
success = true;
} while(false);
return success;
}
static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) {
uint32_t num_cycles_done;
for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) {
if(!expansion_test_app_send_heartbeat(instance)) break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break;
furi_delay_ms(EXPANSION_PROTOCOL_TIMEOUT_MS - 50);
}
return num_cycles_done == num_cycles;
}
static bool expansion_test_app_stop_rpc(ExpansionTestApp* instance) {
bool success = false;
do {
if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStopRpc))
break;
if(!expansion_test_app_receive_frame(instance, &instance->frame)) break;
if(!expansion_test_app_is_success_response(&instance->frame)) break;
success = true;
} while(false);
return success;
}
int32_t expansion_test_app(void* p) {
UNUSED(p);
ExpansionTestApp* instance = expansion_test_app_alloc();
expansion_test_app_start(instance);
bool success = false;
do {
if(!expansion_test_app_send_presence(instance)) break;
if(!expansion_test_app_wait_ready(instance)) break;
if(!expansion_test_app_handshake(instance)) break;
if(!expansion_test_app_start_rpc(instance)) break;
if(!expansion_test_app_rpc_mkdir(instance)) break;
if(!expansion_test_app_rpc_write(instance)) break;
if(!expansion_test_app_rpc_alert(instance)) break;
if(!expansion_test_app_idle(instance, 10)) break;
if(!expansion_test_app_stop_rpc(instance)) break;
if(!expansion_test_app_idle(instance, 10)) break;
success = true;
} while(false);
expansion_test_app_stop(instance);
expansion_test_app_free(instance);
if(!success) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_error);
furi_record_close(RECORD_NOTIFICATION);
}
return 0;
}

View File

@@ -1,13 +1,14 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <gui/elements.h>
#include <furi_hal_uart.h>
#include <furi_hal_console.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define LINES_ON_SCREEN 6
#define COLUMNS_ON_SCREEN 21
#define TAG "UartEcho"
@@ -22,6 +23,7 @@ typedef struct {
View* view;
FuriThread* worker_thread;
FuriStreamBuffer* rx_stream;
FuriHalSerialHandle* serial_handle;
} UartEchoApp;
typedef struct {
@@ -39,10 +41,16 @@ struct UartDumpModel {
typedef enum {
WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
WorkerEventStop = (1 << 1),
WorkerEventRx = (1 << 2),
WorkerEventRxData = (1 << 2),
WorkerEventRxIdle = (1 << 3),
WorkerEventRxOverrunError = (1 << 4),
WorkerEventRxFramingError = (1 << 5),
WorkerEventRxNoiseError = (1 << 6),
} WorkerEventFlags;
#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx)
#define WORKER_EVENTS_MASK \
(WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \
WorkerEventRxFramingError | WorkerEventRxNoiseError)
const NotificationSequence sequence_notification = {
&message_display_backlight_on,
@@ -91,14 +99,39 @@ static uint32_t uart_echo_exit(void* context) {
return VIEW_NONE;
}
static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
static void
uart_echo_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
furi_assert(context);
UNUSED(handle);
UartEchoApp* app = context;
volatile FuriHalSerialRxEvent event_copy = event;
UNUSED(event_copy);
if(ev == UartIrqEventRXNE) {
WorkerEventFlags flag = 0;
if(event & FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx);
flag |= WorkerEventRxData;
}
if(event & FuriHalSerialRxEventIdle) {
//idle line detected, packet transmission may have ended
flag |= WorkerEventRxIdle;
}
//error detected
if(event & FuriHalSerialRxEventFrameError) {
flag |= WorkerEventRxFramingError;
}
if(event & FuriHalSerialRxEventNoiseError) {
flag |= WorkerEventRxNoiseError;
}
if(event & FuriHalSerialRxEventOverrunError) {
flag |= WorkerEventRxOverrunError;
}
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag);
}
static void uart_echo_push_to_list(UartDumpModel* model, const char data) {
@@ -153,13 +186,13 @@ static int32_t uart_echo_worker(void* context) {
furi_check((events & FuriFlagError) == 0);
if(events & WorkerEventStop) break;
if(events & WorkerEventRx) {
if(events & WorkerEventRxData) {
size_t length = 0;
do {
uint8_t data[64];
length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0);
if(length > 0) {
furi_hal_uart_tx(FuriHalUartIdUSART1, data, length);
furi_hal_serial_tx(app->serial_handle, data, length);
with_view_model(
app->view,
UartDumpModel * model,
@@ -176,6 +209,23 @@ static int32_t uart_echo_worker(void* context) {
with_view_model(
app->view, UartDumpModel * model, { UNUSED(model); }, true);
}
if(events & WorkerEventRxIdle) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect IDLE\r\n", 15);
}
if(events &
(WorkerEventRxOverrunError | WorkerEventRxFramingError | WorkerEventRxNoiseError)) {
if(events & WorkerEventRxOverrunError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect ORE\r\n", 14);
}
if(events & WorkerEventRxFramingError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect FE\r\n", 13);
}
if(events & WorkerEventRxNoiseError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13);
}
}
}
return 0;
@@ -221,9 +271,11 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
furi_thread_start(app->worker_thread);
// Enable uart listener
furi_hal_console_disable();
furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app);
app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
furi_check(app->serial_handle);
furi_hal_serial_init(app->serial_handle, baudrate);
furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true);
return app;
}
@@ -231,12 +283,13 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
static void uart_echo_app_free(UartEchoApp* app) {
furi_assert(app);
furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
furi_thread_join(app->worker_thread);
furi_thread_free(app->worker_thread);
furi_hal_serial_deinit(app->serial_handle);
furi_hal_serial_control_release(app->serial_handle);
// Free views
view_dispatcher_remove_view(app->view_dispatcher, 0);

View File

@@ -0,0 +1,200 @@
#include "../minunit.h"
#include <furi.h>
#include <furi_hal_random.h>
#include <expansion/expansion_protocol.h>
#define EXPANSION_TEST_GARBAGE_MAGIC (0xB19AF)
#define EXPANSION_TEST_GARBAGE_BUF_SIZE (0x100U)
#define EXPANSION_TEST_GARBAGE_ITERATIONS (100U)
MU_TEST(test_expansion_encoded_size) {
ExpansionFrame frame = {};
frame.header.type = ExpansionFrameTypeHeartbeat;
mu_assert_int_eq(1, expansion_frame_get_encoded_size(&frame));
frame.header.type = ExpansionFrameTypeStatus;
mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame));
frame.header.type = ExpansionFrameTypeBaudRate;
mu_assert_int_eq(5, expansion_frame_get_encoded_size(&frame));
frame.header.type = ExpansionFrameTypeControl;
mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame));
frame.header.type = ExpansionFrameTypeData;
for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) {
frame.content.data.size = i;
mu_assert_int_eq(i + 2, expansion_frame_get_encoded_size(&frame));
}
}
MU_TEST(test_expansion_remaining_size) {
ExpansionFrame frame = {};
size_t remaining_size;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
frame.header.type = ExpansionFrameTypeHeartbeat;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size));
mu_assert_int_eq(0, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size));
mu_assert_int_eq(0, remaining_size);
frame.header.type = ExpansionFrameTypeStatus;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size));
mu_assert_int_eq(0, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size));
mu_assert_int_eq(0, remaining_size);
frame.header.type = ExpansionFrameTypeBaudRate;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size));
mu_assert_int_eq(4, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 5, &remaining_size));
mu_assert_int_eq(0, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size));
mu_assert_int_eq(0, remaining_size);
frame.header.type = ExpansionFrameTypeControl;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size));
mu_assert_int_eq(0, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size));
mu_assert_int_eq(0, remaining_size);
frame.header.type = ExpansionFrameTypeData;
frame.content.data.size = EXPANSION_PROTOCOL_MAX_DATA_SIZE;
mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size));
mu_assert_int_eq(1, remaining_size);
mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size));
mu_assert_int_eq(EXPANSION_PROTOCOL_MAX_DATA_SIZE, remaining_size);
for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) {
mu_check(expansion_frame_get_remaining_size(&frame, i + 2, &remaining_size));
mu_assert_int_eq(EXPANSION_PROTOCOL_MAX_DATA_SIZE - i, remaining_size);
}
mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size));
mu_assert_int_eq(0, remaining_size);
}
typedef struct {
void* data_out;
size_t size_available;
size_t size_sent;
} TestExpansionSendStream;
static size_t test_expansion_send_callback(const uint8_t* data, size_t data_size, void* context) {
TestExpansionSendStream* stream = context;
const size_t size_sent = MIN(data_size, stream->size_available);
memcpy(stream->data_out + stream->size_sent, data, size_sent);
stream->size_available -= size_sent;
stream->size_sent += size_sent;
return size_sent;
}
typedef struct {
const void* data_in;
size_t size_available;
size_t size_received;
} TestExpansionReceiveStream;
static size_t test_expansion_receive_callback(uint8_t* data, size_t data_size, void* context) {
TestExpansionReceiveStream* stream = context;
const size_t size_received = MIN(data_size, stream->size_available);
memcpy(data, stream->data_in + stream->size_received, size_received);
stream->size_available -= size_received;
stream->size_received += size_received;
return size_received;
}
MU_TEST(test_expansion_encode_decode_frame) {
const ExpansionFrame frame_in = {
.header.type = ExpansionFrameTypeData,
.content.data.size = 8,
.content.data.bytes = {0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xca, 0xfe},
};
uint8_t encoded_data[sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)];
memset(encoded_data, 0, sizeof(encoded_data));
TestExpansionSendStream send_stream = {
.data_out = &encoded_data,
.size_available = sizeof(encoded_data),
.size_sent = 0,
};
const size_t encoded_size = expansion_frame_get_encoded_size(&frame_in);
mu_assert_int_eq(
expansion_protocol_encode(&frame_in, test_expansion_send_callback, &send_stream),
ExpansionProtocolStatusOk);
mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), send_stream.size_sent);
mu_assert_int_eq(
expansion_protocol_get_checksum((const uint8_t*)&frame_in, encoded_size),
encoded_data[encoded_size]);
mu_assert_mem_eq(&frame_in, &encoded_data, encoded_size);
TestExpansionReceiveStream stream = {
.data_in = encoded_data,
.size_available = send_stream.size_sent,
.size_received = 0,
};
ExpansionFrame frame_out;
mu_assert_int_eq(
expansion_protocol_decode(&frame_out, test_expansion_receive_callback, &stream),
ExpansionProtocolStatusOk);
mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), stream.size_received);
mu_assert_mem_eq(&frame_in, &frame_out, encoded_size);
}
MU_TEST(test_expansion_garbage_input) {
uint8_t garbage_data[EXPANSION_TEST_GARBAGE_BUF_SIZE];
for(uint32_t i = 0; i < EXPANSION_TEST_GARBAGE_ITERATIONS; ++i) {
furi_hal_random_fill_buf(garbage_data, sizeof(garbage_data));
size_t remaining_size = EXPANSION_TEST_GARBAGE_MAGIC;
if(expansion_frame_get_remaining_size(
(ExpansionFrame*)garbage_data, sizeof(garbage_data), &remaining_size)) {
// If by chance the garbage data is a valid frame, then the result
// must be 0 because the amount of data provided is more than enough
mu_assert_int_eq(0, remaining_size);
} else {
// If the frame is invalid, the remaining_size parameter should be untouched
mu_assert_int_eq(EXPANSION_TEST_GARBAGE_MAGIC, remaining_size);
}
}
}
MU_TEST_SUITE(test_expansion_suite) {
MU_RUN_TEST(test_expansion_encoded_size);
MU_RUN_TEST(test_expansion_remaining_size);
MU_RUN_TEST(test_expansion_encode_decode_frame);
MU_RUN_TEST(test_expansion_garbage_input);
}
int run_minunit_test_expansion() {
MU_RUN_SUITE(test_expansion_suite);
return MU_EXIT_CODE;
}

View File

@@ -1,8 +1,11 @@
#include "furi_hal_rtc.h"
#include <stdint.h>
#include <stdio.h>
#include <furi.h>
#include <furi_hal.h>
#include <lp5562_reg.h>
#include "../minunit.h"
#include <stdlib.h>
#define DATA_SIZE 4
#define EEPROM_ADDRESS 0b10101000
@@ -211,6 +214,37 @@ MU_TEST(furi_hal_i2c_ext_eeprom) {
}
}
MU_TEST(furi_hal_rtc_timestamp2datetime_min) {
uint32_t test_value = 0;
FuriHalRtcDateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0};
FuriHalRtcDateTime result = {0};
furi_hal_rtc_timestamp_to_datetime(test_value, &result);
mu_assert_mem_eq(&min_datetime_expected, &result, sizeof(result));
}
MU_TEST(furi_hal_rtc_timestamp2datetime_max) {
uint32_t test_value = UINT32_MAX;
FuriHalRtcDateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0};
FuriHalRtcDateTime result = {0};
furi_hal_rtc_timestamp_to_datetime(test_value, &result);
mu_assert_mem_eq(&max_datetime_expected, &result, sizeof(result));
}
MU_TEST(furi_hal_rtc_timestamp2datetime2timestamp) {
uint32_t test_value = random();
FuriHalRtcDateTime datetime = {0};
furi_hal_rtc_timestamp_to_datetime(test_value, &datetime);
uint32_t result = furi_hal_rtc_datetime_to_timestamp(&datetime);
mu_assert_int_eq(test_value, result);
}
MU_TEST_SUITE(furi_hal_i2c_int_suite) {
MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown);
MU_RUN_TEST(furi_hal_i2c_int_1b);
@@ -224,8 +258,15 @@ MU_TEST_SUITE(furi_hal_i2c_ext_suite) {
MU_RUN_TEST(furi_hal_i2c_ext_eeprom);
}
MU_TEST_SUITE(furi_hal_rtc_datetime_suite) {
MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_min);
MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_max);
MU_RUN_TEST(furi_hal_rtc_timestamp2datetime2timestamp);
}
int run_minunit_test_furi_hal() {
MU_RUN_SUITE(furi_hal_i2c_int_suite);
MU_RUN_SUITE(furi_hal_i2c_ext_suite);
MU_RUN_SUITE(furi_hal_rtc_datetime_suite);
return MU_EXIT_CODE;
}

View File

@@ -209,6 +209,25 @@ const int8_t indala26_test_timings[INDALA26_EMULATION_TIMINGS_COUNT] = {
-1, 1, -1, 1, -1, 1, -1, 1,
};
#define FDXB_TEST_DATA \
{ 0x44, 0x88, 0x23, 0xF2, 0x5A, 0x6F, 0x00, 0x01, 0x00, 0x00, 0x00 }
#define FDXB_TEST_DATA_SIZE 11
#define FDXB_TEST_EMULATION_TIMINGS_COUNT (206)
const int8_t fdxb_test_timings[FDXB_TEST_EMULATION_TIMINGS_COUNT] = {
32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16,
-16, 16, -32, 16, -16, 32, -16, 16, -16, 16, -16, 16, -32, 16, -16, 16, -16, 32, -32,
16, -16, 16, -16, 16, -16, 32, -16, 16, -16, 16, -16, 16, -32, 16, -16, 16, -16, 32,
-16, 16, -16, 16, -16, 16, -32, 32, -32, 32, -32, 32, -32, 16, -16, 16, -16, 32, -16,
16, -32, 16, -16, 32, -16, 16, -32, 32, -16, 16, -32, 16, -16, 32, -16, 16, -32, 32,
-16, 16, -32, 32, -32, 32, -32, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16,
16, -16, 16, -16, 32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16,
-32, 32, -32, 32, -32, 32, -32, 16, -16, 32, -32, 32, -16, 16, -16, 16, -32, 32, -32,
32, -32, 32, -32, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16,
-16, 32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -32,
16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16,
};
MU_TEST(test_lfrfid_protocol_em_read_simple) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
mu_assert_int_eq(EM_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolEM4100));
@@ -445,6 +464,73 @@ MU_TEST(test_lfrfid_protocol_inadala26_emulate_simple) {
protocol_dict_free(dict);
}
MU_TEST(test_lfrfid_protocol_fdxb_emulate_simple) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB));
mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB));
mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB));
const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA;
protocol_dict_set_data(dict, LFRFIDProtocolFDXB, data, FDXB_TEST_DATA_SIZE);
mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolFDXB));
for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT; i++) {
LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolFDXB);
if(level_duration_get_level(level_duration)) {
mu_assert_int_eq(fdxb_test_timings[i], level_duration_get_duration(level_duration));
} else {
mu_assert_int_eq(fdxb_test_timings[i], -level_duration_get_duration(level_duration));
}
}
protocol_dict_free(dict);
}
MU_TEST(test_lfrfid_protocol_fdxb_read_simple) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB));
mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB));
mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB));
const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA;
protocol_dict_decoders_start(dict);
ProtocolId protocol = PROTOCOL_NO;
PulseGlue* pulse_glue = pulse_glue_alloc();
for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT * 10; i++) {
bool pulse_pop = pulse_glue_push(
pulse_glue,
fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT] >= 0,
abs(fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT]) *
LF_RFID_READ_TIMING_MULTIPLIER);
if(pulse_pop) {
uint32_t length, period;
pulse_glue_pop(pulse_glue, &length, &period);
protocol = protocol_dict_decoders_feed(dict, true, period);
if(protocol != PROTOCOL_NO) break;
protocol = protocol_dict_decoders_feed(dict, false, length - period);
if(protocol != PROTOCOL_NO) break;
}
}
pulse_glue_free(pulse_glue);
mu_assert_int_eq(LFRFIDProtocolFDXB, protocol);
uint8_t received_data[FDXB_TEST_DATA_SIZE] = {0};
protocol_dict_get_data(dict, protocol, received_data, FDXB_TEST_DATA_SIZE);
mu_assert_mem_eq(data, received_data, FDXB_TEST_DATA_SIZE);
protocol_dict_free(dict);
}
MU_TEST_SUITE(test_lfrfid_protocols_suite) {
MU_RUN_TEST(test_lfrfid_protocol_em_read_simple);
MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple);
@@ -456,6 +542,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) {
MU_RUN_TEST(test_lfrfid_protocol_ioprox_xsf_emulate_simple);
MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple);
MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple);
MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple);
}
int run_minunit_test_lfrfid_protocols() {

View File

@@ -229,17 +229,17 @@ typedef struct {
size_t pos;
} SubGhzHalAsyncTxTest;
#define SUBGHZ_HAL_TEST_DURATION 1
#define SUBGHZ_HAL_TEST_DURATION 3
static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
SubGhzHalAsyncTxTest* test = context;
bool is_odd = test->pos % 2;
if(test->type == SubGhzHalAsyncTxTestTypeNormal) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
@@ -249,36 +249,36 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
if(test->pos == 0) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) {
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) {
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
@@ -292,20 +292,20 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
} else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) {
test->pos++;
return level_duration_reset();
} else {
@@ -332,6 +332,8 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
while(!furi_hal_subghz_is_async_tx_complete()) {
if(furi_hal_cortex_timer_is_expired(timer)) {
furi_hal_subghz_stop_async_tx();
furi_hal_subghz_sleep();
return false;
}
furi_delay_ms(10);

View File

@@ -29,6 +29,7 @@ int run_minunit_test_bit_lib();
int run_minunit_test_float_tools();
int run_minunit_test_bt();
int run_minunit_test_dialogs_file_browser_options();
int run_minunit_test_expansion();
typedef int (*UnitTestEntry)();
@@ -60,6 +61,7 @@ const UnitTest unit_tests[] = {
{.name = "bt", .entry = run_minunit_test_bt},
{.name = "dialogs_file_browser_options",
.entry = run_minunit_test_dialogs_file_browser_options},
{.name = "expansion", .entry = run_minunit_test_expansion},
};
void minunit_print_progress() {

View File

@@ -17,18 +17,18 @@
#define TAG "SubGhzDeviceCc1101Ext"
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO (&gpio_ext_pb2)
#define SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO &gpio_ext_pc3
#define SUBGHZ_DEVICE_CC1101_EXT_FORCE_DANGEROUS_RANGE false
#define SUBGHZ_DEVICE_CC1101_CONFIG_VER 1
/* DMA Channels definition */
#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3
#define SUBGHZ_DEVICE_CC1101_EXT_DMA (DMA2)
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL (LL_DMA_CHANNEL_3)
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL (LL_DMA_CHANNEL_4)
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL (LL_DMA_CHANNEL_5)
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ (FuriHalInterruptIdDma2Ch3)
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \
SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL
#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \
@@ -37,10 +37,10 @@
SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL
/** Low level buffer dimensions and guard times */
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256)
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256u)
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \
(SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2)
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 << 1
#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME (999u >> 1)
/** SubGhz state */
typedef enum {
@@ -48,7 +48,6 @@ typedef enum {
SubGhzDeviceCC1101ExtStateIdle, /**< Idle, energy save mode */
SubGhzDeviceCC1101ExtStateAsyncRx, /**< Async RX started */
SubGhzDeviceCC1101ExtStateAsyncTx, /**< Async TX started, DMA and timer is on */
SubGhzDeviceCC1101ExtStateAsyncTxEnd, /**< Async TX complete, cleanup needed */
} SubGhzDeviceCC1101ExtState;
/** SubGhz regulation, receive transmission on the current frequency for the
@@ -58,13 +57,25 @@ typedef enum {
SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/
} SubGhzDeviceCC1101ExtRegulation;
typedef enum {
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle,
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset,
SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun,
} SubGhzDeviceCC1101ExtAsyncTxMiddlewareState;
typedef struct {
SubGhzDeviceCC1101ExtAsyncTxMiddlewareState state;
bool is_odd_level;
uint32_t adder_duration;
} SubGhzDeviceCC1101ExtAsyncTxMiddleware;
typedef struct {
uint32_t* buffer;
LevelDuration carry_ld;
SubGhzDeviceCC1101ExtCallback callback;
void* callback_context;
uint32_t gpio_tx_buff[2];
uint32_t debug_gpio_buff[2];
SubGhzDeviceCC1101ExtAsyncTxMiddleware middleware;
} SubGhzDeviceCC1101ExtAsyncTx;
typedef struct {
@@ -283,8 +294,8 @@ void subghz_device_cc1101_ext_dump_state() {
void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) {
//load config
subghz_device_cc1101_ext_reset();
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
uint32_t i = 0;
uint8_t pa[8] = {0};
while(preset_data[i]) {
@@ -313,8 +324,8 @@ void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) {
}
void subghz_device_cc1101_ext_load_registers(const uint8_t* data) {
subghz_device_cc1101_ext_reset();
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
uint32_t i = 0;
while(data[i]) {
cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]);
@@ -396,6 +407,7 @@ void subghz_device_cc1101_ext_reset() {
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle);
// Warning: push pull cc1101 clock output on GD0
cc1101_write_reg(
subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance);
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
@@ -404,6 +416,9 @@ void subghz_device_cc1101_ext_reset() {
void subghz_device_cc1101_ext_idle() {
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle);
//waiting for the chip to switch to IDLE mode
furi_check(cc1101_wait_status_state(
subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000));
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
if(subghz_device_cc1101_ext->power_amp) {
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0);
@@ -413,6 +428,9 @@ void subghz_device_cc1101_ext_idle() {
void subghz_device_cc1101_ext_rx() {
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle);
//waiting for the chip to switch to Rx mode
furi_check(
cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000));
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
if(subghz_device_cc1101_ext->power_amp) {
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0);
@@ -423,6 +441,9 @@ bool subghz_device_cc1101_ext_tx() {
if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false;
furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle);
cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle);
//waiting for the chip to switch to Tx mode
furi_check(
cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000));
furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
if(subghz_device_cc1101_ext->power_amp) {
furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 1);
@@ -616,50 +637,90 @@ void subghz_device_cc1101_ext_stop_async_rx() {
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) {
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx);
while(samples > 0) {
bool is_odd = samples % 2;
LevelDuration ld;
if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) {
ld = subghz_device_cc1101_ext->async_tx.callback(
subghz_device_cc1101_ext->async_tx.callback_context);
} else {
ld = subghz_device_cc1101_ext->async_tx.carry_ld;
subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset();
void subghz_device_cc1101_ext_async_tx_middleware_idle(
SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware) {
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle;
middleware->is_odd_level = false;
middleware->adder_duration = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
}
static inline uint32_t subghz_device_cc1101_ext_async_tx_middleware_get_duration(
SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware,
SubGhzDeviceCC1101ExtCallback callback) {
uint32_t ret = 0;
bool is_level = false;
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset) return 0;
while(1) {
LevelDuration ld = callback(subghz_device_cc1101_ext->async_tx.callback_context);
if(level_duration_is_reset(ld)) {
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset;
if(!middleware->is_odd_level) {
return 0;
} else {
return middleware->adder_duration;
}
} else if(level_duration_is_wait(ld)) {
middleware->is_odd_level = !middleware->is_odd_level;
ret = middleware->adder_duration + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
middleware->adder_duration = 0;
return ret;
}
if(level_duration_is_wait(ld)) {
*buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
buffer++;
samples--;
} else if(level_duration_is_reset(ld)) {
is_level = level_duration_get_level(ld);
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle) {
if(is_level != middleware->is_odd_level) {
middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun;
middleware->is_odd_level = is_level;
middleware->adder_duration = level_duration_get_duration(ld);
return SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
} else {
continue;
}
}
if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun) {
if(is_level == middleware->is_odd_level) {
middleware->adder_duration += level_duration_get_duration(ld);
continue;
} else {
middleware->is_odd_level = is_level;
ret = middleware->adder_duration;
middleware->adder_duration = level_duration_get_duration(ld);
return ret;
}
}
}
}
static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) {
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx);
while(samples > 0) {
volatile uint32_t duration = subghz_device_cc1101_ext_async_tx_middleware_get_duration(
&subghz_device_cc1101_ext->async_tx.middleware,
subghz_device_cc1101_ext->async_tx.callback);
if(duration == 0) {
*buffer = 0;
buffer++;
samples--;
LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
LL_TIM_EnableIT_UPDATE(TIM17);
if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) {
LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA);
}
if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) {
LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA);
}
break;
} else {
bool level = level_duration_get_level(ld);
// Inject guard time if level is incorrect
if(is_odd != level) {
*buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME;
buffer++;
samples--;
// Special case: prevent buffer overflow if sample is last
if(samples == 0) {
subghz_device_cc1101_ext->async_tx.carry_ld = ld;
break;
}
}
uint32_t duration = level_duration_get_duration(ld);
furi_assert(duration > 0);
*buffer = duration >> 1;
// Lowest possible value is 4us
if(duration < 4) duration = 4;
// Divide by 2 since timer resolution is 2us
// Subtract 1 since we counting from 0
*buffer = (duration >> 1) - 1;
buffer++;
samples--;
}
@@ -688,20 +749,6 @@ static void subghz_device_cc1101_ext_async_tx_dma_isr() {
#endif
}
static void subghz_device_cc1101_ext_async_tx_timer_isr() {
if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) {
if(LL_TIM_GetAutoReload(TIM17) == 0) {
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF);
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
if(subghz_device_cc1101_ext->async_mirror_pin != NULL)
furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false);
LL_TIM_DisableCounter(TIM17);
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd;
}
LL_TIM_ClearFlag_UPDATE(TIM17);
}
}
bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context) {
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle);
furi_assert(callback);
@@ -730,7 +777,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF,
LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT |
LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD |
LL_DMA_MODE_NORMAL);
LL_DMA_PRIORITY_VERYHIGH);
LL_DMA_SetDataLength(
SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL);
LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMAMUX_REQ_TIM17_UP);
@@ -746,16 +793,15 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
// Configure TIM
// Set the timer resolution to 2 us
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP);
LL_TIM_SetAutoReload(TIM17, 0xFFFF);
LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1);
LL_TIM_SetAutoReload(TIM17, 500);
LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1);
LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_DisableARRPreload(TIM17);
furi_hal_interrupt_set_isr(
FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL);
subghz_device_cc1101_ext_async_tx_middleware_idle(
&subghz_device_cc1101_ext->async_tx.middleware);
subghz_device_cc1101_ext_async_tx_refill(
subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL);
@@ -801,7 +847,6 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
// Start counter
LL_TIM_EnableDMAReq_UPDATE(TIM17);
LL_TIM_GenerateEvent_UPDATE(TIM17);
subghz_device_cc1101_ext_tx();
@@ -812,19 +857,22 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
}
bool subghz_device_cc1101_ext_is_async_tx_complete() {
return subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd;
return (
(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx) &&
(LL_TIM_GetAutoReload(TIM17) == 0));
}
void subghz_device_cc1101_ext_stop_async_tx() {
furi_assert(
subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx ||
subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd);
furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx);
// Shutdown radio
subghz_device_cc1101_ext_idle();
// Deinitialize GPIO
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
// Deinitialize Timer
FURI_CRITICAL_ENTER();
furi_hal_bus_disable(FuriHalBusTIM17);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL);
@@ -833,17 +881,11 @@ void subghz_device_cc1101_ext_stop_async_tx() {
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF);
furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL);
// Deinitialize GPIO
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
// Stop debug
if(subghz_device_cc1101_ext_stop_debug()) {
LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF);
}
FURI_CRITICAL_EXIT();
free(subghz_device_cc1101_ext->async_tx.buffer);
subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle;

View File

@@ -153,7 +153,9 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) {
archive_get_items(browser, furi_string_get_cstr(browser->path));
if(!archive_file_get_array_size(browser) && archive_is_home(browser)) {
ArchiveTabEnum tab = archive_get_tab(browser);
if(!archive_file_get_array_size(browser) && archive_is_home(browser) &&
(tab != ArchiveTabBrowser)) {
archive_switch_tab(browser, TAB_LEFT);
} else {
with_view_model(
@@ -220,7 +222,8 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) {
},
false);
if((items_cnt == 0) && (archive_is_home(browser))) {
ArchiveTabEnum tab = archive_get_tab(browser);
if((items_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) {
archive_switch_tab(browser, TAB_LEFT);
}

View File

@@ -585,6 +585,10 @@ static bool archive_view_input(InputEvent* event, void* context) {
((model->item_idx - scroll_speed) + model->item_cnt) %
model->item_cnt;
}
// Fix for empty folders, we can't select -1 item
if(model->item_idx < 0) {
model->item_idx = 0;
}
if(is_file_list_load_required(model)) {
model->list_loading = true;
browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context);

View File

@@ -3,7 +3,7 @@ App(
name="GPIO",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="gpio_app",
stack_size=1 * 1024,
stack_size=2 * 1024,
icon="A_GPIO_14",
order=50,
fap_libs=["assets"],

View File

@@ -24,6 +24,9 @@ static void gpio_app_tick_event_callback(void* context) {
GpioApp* gpio_app_alloc() {
GpioApp* app = malloc(sizeof(GpioApp));
app->expansion = furi_record_open(RECORD_EXPANSION);
expansion_disable(app->expansion);
app->gui = furi_record_open(RECORD_GUI);
app->gpio_items = gpio_items_alloc();
@@ -99,6 +102,9 @@ void gpio_app_free(GpioApp* app) {
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
expansion_enable(app->expansion);
furi_record_close(RECORD_EXPANSION);
gpio_items_free(app->gpio_items);
free(app);
}

View File

@@ -17,8 +17,10 @@
#include "views/gpio_test.h"
#include "views/gpio_usb_uart.h"
#include <assets_icons.h>
#include <expansion/expansion.h>
struct GpioApp {
Expansion* expansion;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;

View File

@@ -46,7 +46,7 @@ void line_ensure_flow_invariant(GpioApp* app) {
// selected. This function enforces that invariant by resetting flow_pins
// to None if it is configured to 16,15 when LPUART is selected.
uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4;
uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalSerialIdLpuart ? 3 : 4;
VariableItem* item = app->var_item_flow;
variable_item_set_values_count(item, available_flow_pins);
@@ -77,9 +77,9 @@ static void line_port_cb(VariableItem* item) {
variable_item_set_current_value_text(item, uart_ch[index]);
if(index == 0)
app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1;
app->usb_uart_cfg->uart_ch = FuriHalSerialIdUsart;
else if(index == 1)
app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1;
app->usb_uart_cfg->uart_ch = FuriHalSerialIdLpuart;
line_ensure_flow_invariant(app);
view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet);

View File

@@ -29,17 +29,18 @@ typedef enum {
WorkerEvtTxStop = (1 << 2),
WorkerEvtCdcRx = (1 << 3),
WorkerEvtCdcTxComplete = (1 << 4),
WorkerEvtCfgChange = (1 << 4),
WorkerEvtCfgChange = (1 << 5),
WorkerEvtLineCfgSet = (1 << 5),
WorkerEvtCtrlLineSet = (1 << 6),
WorkerEvtLineCfgSet = (1 << 6),
WorkerEvtCtrlLineSet = (1 << 7),
} WorkerEvtFlags;
#define WORKER_ALL_RX_EVENTS \
(WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \
WorkerEvtCtrlLineSet)
WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete)
#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx)
struct UsbUartBridge {
@@ -50,6 +51,7 @@ struct UsbUartBridge {
FuriThread* tx_thread;
FuriStreamBuffer* rx_stream;
FuriHalSerialHandle* serial_handle;
FuriMutex* usb_mutex;
@@ -80,11 +82,23 @@ static const CdcCallbacks cdc_cb = {
static int32_t usb_uart_tx_thread(void* context);
static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
static void usb_uart_on_irq_rx_dma_cb(
FuriHalSerialHandle* handle,
FuriHalSerialRxEvent ev,
size_t size,
void* context) {
UsbUartBridge* usb_uart = (UsbUartBridge*)context;
if(ev == UartIrqEventRXNE) {
furi_stream_buffer_send(usb_uart->rx_stream, &data, 1, 0);
if(ev & (FuriHalSerialRxEventData | FuriHalSerialRxEventIdle)) {
uint8_t data[FURI_HAL_SERIAL_DMA_BUFFER_SIZE] = {0};
while(size) {
size_t ret = furi_hal_serial_dma_rx(
handle,
data,
(size > FURI_HAL_SERIAL_DMA_BUFFER_SIZE) ? FURI_HAL_SERIAL_DMA_BUFFER_SIZE : size);
furi_stream_buffer_send(usb_uart->rx_stream, data, ret, 0);
size -= ret;
};
furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone);
}
}
@@ -116,32 +130,33 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
}
static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) {
if(uart_ch == FuriHalUartIdUSART1) {
furi_hal_console_disable();
} else if(uart_ch == FuriHalUartIdLPUART1) {
furi_hal_uart_init(uart_ch, 115200);
}
furi_hal_uart_set_irq_cb(uart_ch, usb_uart_on_irq_cb, usb_uart);
furi_assert(!usb_uart->serial_handle);
usb_uart->serial_handle = furi_hal_serial_control_acquire(uart_ch);
furi_assert(usb_uart->serial_handle);
furi_hal_serial_init(usb_uart->serial_handle, 115200);
furi_hal_serial_dma_rx_start(
usb_uart->serial_handle, usb_uart_on_irq_rx_dma_cb, usb_uart, false);
}
static void usb_uart_serial_deinit(UsbUartBridge* usb_uart, uint8_t uart_ch) {
UNUSED(usb_uart);
furi_hal_uart_set_irq_cb(uart_ch, NULL, NULL);
if(uart_ch == FuriHalUartIdUSART1)
furi_hal_console_enable();
else if(uart_ch == FuriHalUartIdLPUART1)
furi_hal_uart_deinit(uart_ch);
static void usb_uart_serial_deinit(UsbUartBridge* usb_uart) {
furi_assert(usb_uart->serial_handle);
furi_hal_serial_deinit(usb_uart->serial_handle);
furi_hal_serial_control_release(usb_uart->serial_handle);
usb_uart->serial_handle = NULL;
}
static void usb_uart_set_baudrate(UsbUartBridge* usb_uart, uint32_t baudrate) {
if(baudrate != 0) {
furi_hal_uart_set_br(usb_uart->cfg.uart_ch, baudrate);
furi_hal_serial_set_br(usb_uart->serial_handle, baudrate);
usb_uart->st.baudrate_cur = baudrate;
} else {
struct usb_cdc_line_coding* line_cfg =
furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch);
if(line_cfg->dwDTERate > 0) {
furi_hal_uart_set_br(usb_uart->cfg.uart_ch, line_cfg->dwDTERate);
furi_hal_serial_set_br(usb_uart->serial_handle, line_cfg->dwDTERate);
usb_uart->st.baudrate_cur = line_cfg->dwDTERate;
}
}
@@ -191,7 +206,7 @@ static int32_t usb_uart_worker(void* context) {
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
furi_check(!(events & FuriFlagError));
if(events & WorkerEvtStop) break;
if(events & WorkerEvtRxDone) {
if(events & (WorkerEvtRxDone | WorkerEvtCdcTxComplete)) {
size_t len = furi_stream_buffer_receive(
usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0);
if(len > 0) {
@@ -223,7 +238,7 @@ static int32_t usb_uart_worker(void* context) {
furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop);
furi_thread_join(usb_uart->tx_thread);
usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch);
usb_uart_serial_deinit(usb_uart);
usb_uart_serial_init(usb_uart, usb_uart->cfg_new.uart_ch);
usb_uart->cfg.uart_ch = usb_uart->cfg_new.uart_ch;
@@ -274,7 +289,7 @@ static int32_t usb_uart_worker(void* context) {
}
}
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch);
usb_uart_serial_deinit(usb_uart);
furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
@@ -320,18 +335,10 @@ static int32_t usb_uart_tx_thread(void* context) {
if(usb_uart->cfg.software_de_re != 0)
furi_hal_gpio_write(USB_USART_DE_RE_PIN, false);
furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len);
furi_hal_serial_tx(usb_uart->serial_handle, data, len);
if(usb_uart->cfg.software_de_re != 0) {
//TODO: FL-3276 port to new USART API
if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) {
while(!LL_USART_IsActiveFlag_TC(USART1))
;
} else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) {
while(!LL_LPUART_IsActiveFlag_TC(LPUART1))
;
}
furi_hal_serial_tx_wait_complete(usb_uart->serial_handle);
furi_hal_gpio_write(USB_USART_DE_RE_PIN, true);
}
}
@@ -345,6 +352,7 @@ static int32_t usb_uart_tx_thread(void* context) {
static void vcp_on_cdc_tx_complete(void* context) {
UsbUartBridge* usb_uart = (UsbUartBridge*)context;
furi_semaphore_release(usb_uart->tx_sem);
furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCdcTxComplete);
}
static void vcp_on_cdc_rx(void* context) {

View File

@@ -1,6 +1,30 @@
#include "lfrfid_i.h"
#include <dolphin/dolphin.h>
//TODO: use .txt file in resources for passwords.
const uint32_t default_passwords[] = {
0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F, 0x89A69E60,
0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752, 0x4E457854, 0x44B44CAE,
0x88661858, 0xE9920427, 0x575F4F4B, 0x50520901, 0x20206666, 0x65857569, 0x5469616E, 0x7686962A,
0xC0F5009A, 0x07CEE75D, 0xfeedbeef, 0xdeadc0de, 0x00000000, 0x11111111, 0x22222222, 0x33333333,
0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB,
0xCCCCCCCC, 0xDDDDDDDD, 0xEEEEEEEE, 0xFFFFFFFF, 0xa0a1a2a3, 0xb0b1b2b3, 0x50415353, 0x00000001,
0x00000002, 0x0000000a, 0x0000000b, 0x01020304, 0x02030405, 0x03040506, 0x04050607, 0x05060708,
0x06070809, 0x0708090A, 0x08090A0B, 0x090A0B0C, 0x0A0B0C0D, 0x0B0C0D0E, 0x0C0D0E0F, 0x01234567,
0x12345678, 0x10000000, 0x20000000, 0x30000000, 0x40000000, 0x50000000, 0x60000000, 0x70000000,
0x80000000, 0x90000000, 0xA0000000, 0xB0000000, 0xC0000000, 0xD0000000, 0xE0000000, 0xF0000000,
0x10101010, 0x01010101, 0x11223344, 0x22334455, 0x33445566, 0x44556677, 0x55667788, 0x66778899,
0x778899AA, 0x8899AABB, 0x99AABBCC, 0xAABBCCDD, 0xBBCCDDEE, 0xCCDDEEFF, 0x0CB7E7FC, 0xFABADA11,
0x87654321, 0x12341234, 0x69696969, 0x12121212, 0x12344321, 0x1234ABCD, 0x11112222, 0x13131313,
0x10041004, 0x31415926, 0xabcd1234, 0x20002000, 0x19721972, 0xaa55aa55, 0x55aa55aa, 0x4f271149,
0x07d7bb0b, 0x9636ef8f, 0xb5f44686, 0x9E3779B9, 0xC6EF3720, 0x7854794A, 0xF1EA5EED, 0x69314718,
0x57721566, 0x93C467E3, 0x27182818, 0x50415353};
const uint32_t* lfrfid_get_t5577_default_passwords(uint8_t* len) {
*len = sizeof(default_passwords) / sizeof(uint32_t);
return default_passwords;
}
static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
LfRfid* app = context;

View File

@@ -25,11 +25,13 @@ void lfrfid_on_system_start() {
static void lfrfid_cli_print_usage() {
printf("Usage:\r\n");
printf("rfid read <optional: normal | indala>\r\n");
printf("rfid <write | emulate> <key_type> <key_data>\r\n");
printf("rfid raw_read <ask | psk> <filename>\r\n");
printf("rfid raw_emulate <filename>\r\n");
printf("rfid raw_analyze <filename>\r\n");
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
printf("rfid <write | emulate> <key_type> <key_data> - write or emulate a card\r\n");
printf("rfid raw_read <ask | psk> <filename> - read and save raw data to a file\r\n");
printf(
"rfid raw_emulate <filename> - emulate raw data (not very useful, but helps debug protocols)\r\n");
printf(
"rfid raw_analyze <filename> - outputs raw data to the cli and tries to decode it (useful for protocol development)\r\n");
};
typedef struct {

View File

@@ -66,6 +66,7 @@ enum LfRfidCustomEvent {
LfRfidEventWriteTooLongToWrite,
LfRfidEventRpcLoadFile,
LfRfidEventRpcSessionClose,
LfRfidEventEmulationTimeExpired,
};
typedef enum {
@@ -98,6 +99,8 @@ struct LfRfid {
uint8_t* old_key_data;
uint8_t* new_key_data;
uint8_t password[4];
RpcAppSystem* rpc_ctx;
LfRfidRpcState rpc_state;
@@ -145,3 +148,5 @@ void lfrfid_popup_timeout_callback(void* context);
void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context);
void lfrfid_text_input_callback(void* context);
const uint32_t* lfrfid_get_t5577_default_passwords(uint8_t* len);

View File

@@ -1,30 +1,13 @@
#include "../lfrfid_i.h"
#include "tools/t5577.h"
#define TAG "Clear T5577"
static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
Popup* popup = app->popup;
char curr_buf[32] = {};
//TODO: use .txt file in resources for passwords.
const uint32_t default_passwords[] = {
0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F,
0x89A69E60, 0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752,
0x4E457854, 0x44B44CAE, 0x88661858, 0xE9920427, 0x575F4F4B, 0x50520901, 0x20206666,
0x65857569, 0x5469616E, 0x7686962A, 0xC0F5009A, 0x07CEE75D, 0xfeedbeef, 0xdeadc0de,
0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666,
0x77777777, 0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD,
0xEEEEEEEE, 0xFFFFFFFF, 0xa0a1a2a3, 0xb0b1b2b3, 0x50415353, 0x00000001, 0x00000002,
0x0000000a, 0x0000000b, 0x01020304, 0x02030405, 0x03040506, 0x04050607, 0x05060708,
0x06070809, 0x0708090A, 0x08090A0B, 0x090A0B0C, 0x0A0B0C0D, 0x0B0C0D0E, 0x0C0D0E0F,
0x01234567, 0x12345678, 0x10000000, 0x20000000, 0x30000000, 0x40000000, 0x50000000,
0x60000000, 0x70000000, 0x80000000, 0x90000000, 0xA0000000, 0xB0000000, 0xC0000000,
0xD0000000, 0xE0000000, 0xF0000000, 0x10101010, 0x01010101, 0x11223344, 0x22334455,
0x33445566, 0x44556677, 0x55667788, 0x66778899, 0x778899AA, 0x8899AABB, 0x99AABBCC,
0xAABBCCDD, 0xBBCCDDEE, 0xCCDDEEFF, 0x0CB7E7FC, 0xFABADA11, 0x87654321, 0x12341234,
0x69696969, 0x12121212, 0x12344321, 0x1234ABCD, 0x11112222, 0x13131313, 0x10041004,
0x31415926, 0xabcd1234, 0x20002000, 0x19721972, 0xaa55aa55, 0x55aa55aa, 0x4f271149,
0x07d7bb0b, 0x9636ef8f, 0xb5f44686, 0x9E3779B9, 0xC6EF3720, 0x7854794A, 0xF1EA5EED,
0x69314718, 0x57721566, 0x93C467E3, 0x27182818, 0x50415353};
const uint8_t default_passwords_len = sizeof(default_passwords) / sizeof(uint32_t);
uint8_t default_passwords_len;
const uint32_t* default_passwords = lfrfid_get_t5577_default_passwords(&default_passwords_len);
popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
@@ -33,14 +16,26 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
LFRFIDT5577 data = {
.block[0] = 0b00000000000101001000000001000000,
.blocks_to_write = 1,
.block[7] = 0,
.mask = 0b10000001,
};
// Clear custom password
uint32_t custom_pass = (app->password[0] << 24) | (app->password[1] << 16) |
(app->password[2] << 8) | (app->password[3]);
snprintf(curr_buf, sizeof(curr_buf), "Custom password");
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
t5577_write_with_mask(&data, 0, true, custom_pass);
furi_delay_ms(8);
// Clear default passwords
for(uint8_t i = 0; i < default_passwords_len; i++) {
snprintf(curr_buf, sizeof(curr_buf), "Pass %d of %d", i, default_passwords_len);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
t5577_write_with_pass(&data, default_passwords[i]);
t5577_write_with_mask(&data, 0, true, default_passwords[i]);
furi_delay_ms(8);
}

View File

@@ -6,6 +6,7 @@ ADD_SCENE(lfrfid, exit_confirm, ExitConfirm)
ADD_SCENE(lfrfid, delete_confirm, DeleteConfirm)
ADD_SCENE(lfrfid, read_key_menu, ReadKeyMenu)
ADD_SCENE(lfrfid, write, Write)
ADD_SCENE(lfrfid, write_and_set_pass, WriteAndSetPass)
ADD_SCENE(lfrfid, write_success, WriteSuccess)
ADD_SCENE(lfrfid, emulate, Emulate)
ADD_SCENE(lfrfid, save_name, SaveName)
@@ -17,6 +18,7 @@ ADD_SCENE(lfrfid, save_type, SaveType)
ADD_SCENE(lfrfid, saved_info, SavedInfo)
ADD_SCENE(lfrfid, clear_t5577, ClearT5577)
ADD_SCENE(lfrfid, clear_t5577_confirm, ClearT5577Confirm)
ADD_SCENE(lfrfid, enter_password, EnterPassword)
ADD_SCENE(lfrfid, delete_success, DeleteSuccess)
ADD_SCENE(lfrfid, extra_actions, ExtraActions)
ADD_SCENE(lfrfid, raw_info, RawInfo)

View File

@@ -1,5 +1,14 @@
#include "../lfrfid_i.h"
#define LFRFID_EMULATION_TIME_MAX_MS (5 * 60 * 1000)
FuriTimer* timer_auto_exit;
void lfrfid_scene_emulate_popup_callback(void* context) {
LfRfid* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventEmulationTimeExpired);
}
void lfrfid_scene_emulate_on_enter(void* context) {
LfRfid* app = context;
Popup* popup = app->popup;
@@ -22,18 +31,38 @@ void lfrfid_scene_emulate_on_enter(void* context) {
lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id);
notification_message(app->notifications, &sequence_blink_start_magenta);
timer_auto_exit =
furi_timer_alloc(lfrfid_scene_emulate_popup_callback, FuriTimerTypeOnce, app);
furi_timer_start(timer_auto_exit, LFRFID_EMULATION_TIME_MAX_MS);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
}
bool lfrfid_scene_emulate_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
LfRfid* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == LfRfidEventEmulationTimeExpired) {
if(!scene_manager_previous_scene(app->scene_manager)) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
} else {
scene_manager_previous_scene(app->scene_manager);
}
consumed = true;
}
}
return consumed;
}
void lfrfid_scene_emulate_on_exit(void* context) {
LfRfid* app = context;
furi_timer_stop(timer_auto_exit);
furi_timer_free(timer_auto_exit);
notification_message(app->notifications, &sequence_blink_stop);
popup_reset(app->popup);
lfrfid_worker_stop(app->lfworker);

View File

@@ -0,0 +1,55 @@
#include "../lfrfid_i.h"
#include "gui/scene_manager.h"
int next_scene;
void lfrfid_scene_enter_password_on_enter(void* context) {
LfRfid* app = context;
ByteInput* byte_input = app->byte_input;
// true - use password for write, false - use password for clear pass
next_scene = scene_manager_get_scene_state(app->scene_manager, LfRfidSceneEnterPassword);
bool password_set = app->password[0] | app->password[1] | app->password[2] | app->password[3];
if(next_scene == LfRfidSceneWriteAndSetPass && !password_set) {
uint8_t password_list_size;
const uint32_t* password_list = lfrfid_get_t5577_default_passwords(&password_list_size);
uint32_t pass = password_list[furi_get_tick() % password_list_size];
for(uint8_t i = 0; i < 4; i++) app->password[i] = (pass >> (8 * i)) & 0xFF;
}
byte_input_set_header_text(byte_input, "Enter the password in hex");
byte_input_set_result_callback(
byte_input, lfrfid_text_input_callback, NULL, app, app->password, 4);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewByteInput);
}
bool lfrfid_scene_enter_password_on_event(void* context, SceneManagerEvent event) {
LfRfid* app = context;
SceneManager* scene_manager = app->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == LfRfidEventNext) {
consumed = true;
scene_manager_next_scene(scene_manager, next_scene);
scene_manager_set_scene_state(scene_manager, LfRfidSceneEnterPassword, 1);
}
} else if(event.type == SceneManagerEventTypeBack) {
uint32_t prev_scenes[] = {LfRfidSceneExtraActions, LfRfidSceneSavedKeyMenu};
scene_manager_set_scene_state(scene_manager, LfRfidSceneEnterPassword, 0);
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, prev_scenes, sizeof(prev_scenes[0]));
}
return consumed;
}
void lfrfid_scene_enter_password_on_exit(void* context) {
UNUSED(context);
}

View File

@@ -80,7 +80,9 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event)
dolphin_deed(DolphinDeedRfidRead);
consumed = true;
} else if(event.event == SubmenuIndexClearT5577) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneClearT5577Confirm);
scene_manager_set_scene_state(
app->scene_manager, LfRfidSceneEnterPassword, LfRfidSceneClearT5577Confirm);
scene_manager_next_scene(app->scene_manager, LfRfidSceneEnterPassword);
consumed = true;
} else if(event.event == SubmenuIndexRAW) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName);

View File

@@ -4,6 +4,7 @@
typedef enum {
SubmenuIndexEmulate,
SubmenuIndexWrite,
SubmenuIndexWriteAndSetPass,
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexInfo,
@@ -23,6 +24,12 @@ void lfrfid_scene_saved_key_menu_on_enter(void* context) {
submenu, "Emulate", SubmenuIndexEmulate, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
submenu, "Write", SubmenuIndexWrite, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
submenu,
"Write and set pass",
SubmenuIndexWriteAndSetPass,
lfrfid_scene_saved_key_menu_submenu_callback,
app);
submenu_add_item(
submenu, "Edit", SubmenuIndexEdit, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
@@ -48,6 +55,11 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite);
consumed = true;
} else if(event.event == SubmenuIndexWriteAndSetPass) {
scene_manager_set_scene_state(
app->scene_manager, LfRfidSceneEnterPassword, LfRfidSceneWriteAndSetPass);
scene_manager_next_scene(app->scene_manager, LfRfidSceneEnterPassword);
consumed = true;
} else if(event.event == SubmenuIndexEdit) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData);
consumed = true;

View File

@@ -0,0 +1,84 @@
#include "../lfrfid_i.h"
#include "gui/scene_manager.h"
static void lfrfid_write_and_set_pass_callback(LFRFIDWorkerWriteResult result, void* context) {
LfRfid* app = context;
uint32_t event = 0;
if(result == LFRFIDWorkerWriteOK) {
event = LfRfidEventWriteOK;
} else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) {
event = LfRfidEventWriteProtocolCannotBeWritten;
} else if(result == LFRFIDWorkerWriteFobCannotBeWritten) {
event = LfRfidEventWriteFobCannotBeWritten;
} else if(result == LFRFIDWorkerWriteTooLongToWrite) {
event = LfRfidEventWriteTooLongToWrite;
}
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
void lfrfid_scene_write_and_set_pass_on_enter(void* context) {
LfRfid* app = context;
Popup* popup = app->popup;
popup_set_header(popup, "Writing\nwith password", 89, 30, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size);
lfrfid_worker_start_thread(app->lfworker);
lfrfid_worker_write_and_set_pass_start(
app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_and_set_pass_callback, app);
notification_message(app->notifications, &sequence_blink_start_magenta);
}
bool lfrfid_scene_write_and_set_pass_on_event(void* context, SceneManagerEvent event) {
LfRfid* app = context;
Popup* popup = app->popup;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == LfRfidEventWriteOK) {
notification_message(app->notifications, &sequence_success);
scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess);
consumed = true;
} else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) {
popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42);
popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop);
popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop);
notification_message(app->notifications, &sequence_blink_start_red);
consumed = true;
} else if(
(event.event == LfRfidEventWriteFobCannotBeWritten) ||
(event.event == LfRfidEventWriteTooLongToWrite)) {
popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42);
popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop);
popup_set_text(
popup,
"Make sure this\ncard is writable\nand not\nprotected.",
3,
17,
AlignLeft,
AlignTop);
notification_message(app->notifications, &sequence_blink_start_yellow);
consumed = true;
}
}
return consumed;
}
void lfrfid_scene_write_and_set_pass_on_exit(void* context) {
LfRfid* app = context;
notification_message(app->notifications, &sequence_blink_stop);
popup_reset(app->popup);
lfrfid_worker_stop(app->lfworker);
lfrfid_worker_stop_thread(app->lfworker);
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size);
}

View File

@@ -146,6 +146,15 @@ App(
sources=["plugins/supported_cards/zolotaya_korona.c"],
)
App(
appid="zolotaya_korona_online_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="zolotaya_korona_online_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/zolotaya_korona_online.c"],
)
App(
appid="hid_parser",
apptype=FlipperAppType.PLUGIN,
@@ -164,6 +173,24 @@ App(
sources=["plugins/supported_cards/washcity.c"],
)
App(
appid="emv_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="emv_plugin_ep",
targets=["f7"],
requires=["nfc", "storage"],
sources=["plugins/supported_cards/emv.c", "helpers/nfc_emv_parser.c"],
)
App(
appid="ndef_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="ndef_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="nfc_start",
targets=["f7"],

View File

@@ -30,4 +30,6 @@ typedef enum {
NfcCustomEventPollerFailure,
NfcCustomEventListenerUpdate,
NfcCustomEventEmulationTimeExpired,
} NfcCustomEvent;

View File

@@ -34,7 +34,7 @@ static bool nfc_emv_parser_search_data(
bool nfc_emv_parser_get_aid_name(
Storage* storage,
uint8_t* aid,
const uint8_t* aid,
uint8_t aid_len,
FuriString* aid_name) {
furi_assert(storage);

View File

@@ -13,7 +13,7 @@
*/
bool nfc_emv_parser_get_aid_name(
Storage* storage,
uint8_t* aid,
const uint8_t* aid,
uint8_t aid_len,
FuriString* aid_name);

View File

@@ -0,0 +1,115 @@
#include "emv.h"
#include "emv_render.h"
#include <nfc/protocols/emv/emv_poller.h>
#include "nfc/nfc_app_i.h"
#include "../nfc_protocol_support_common.h"
#include "../nfc_protocol_support_gui_common.h"
#include "../iso14443_4a/iso14443_4a_i.h"
static void nfc_scene_info_on_enter_emv(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
FuriString* temp_str = furi_string_alloc();
// furi_string_cat_printf(
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_emv_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_emv(NfcApp* instance) {
// Jump to advanced scene right away
scene_manager_next_scene(instance->scene_manager, NfcSceneEmvMoreInfo);
}
static NfcCommand nfc_scene_read_poller_callback_emv(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolEmv);
NfcApp* instance = context;
const EmvPollerEvent* emv_event = event.event_data;
if(emv_event->type == EmvPollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolEmv, nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
return NfcCommandStop;
}
return NfcCommandContinue;
}
static void nfc_scene_read_on_enter_emv(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_emv, instance);
}
static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
FuriString* temp_str = furi_string_alloc();
// furi_string_cat_printf(
// temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_emv_info(data, NfcProtocolFormatTypeShort, temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
// static void nfc_scene_emulate_on_enter_emv(NfcApp* instance) {
// const Iso14443_4aData* iso14443_4a_data =
// nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);
// instance->listener =
// nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
// nfc_listener_start(
// instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
// }
const NfcProtocolSupportBase nfc_protocol_support_emv = {
.features = NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_emv,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_emv,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_emv,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_success =
{
.on_enter = nfc_scene_read_success_on_enter_emv,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_emv;

View File

@@ -0,0 +1,186 @@
#include "emv_render.h"
#include "../iso14443_4a/iso14443_4a_render.h"
#include "nfc/nfc_app_i.h"
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) {
nfc_render_emv_header(str);
nfc_render_emv_uid(
data->iso14443_4a_data->iso14443_3a_data->uid,
data->iso14443_4a_data->iso14443_3a_data->uid_len,
str);
if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, str);
}
void nfc_render_emv_header(FuriString* str) {
furi_string_cat_printf(str, "\e#%s\n", "EMV");
}
void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) {
if(uid_len == 0) return;
furi_string_cat_printf(str, "UID: ");
for(uint8_t i = 0; i < uid_len; i++) {
furi_string_cat_printf(str, "%02X ", uid[i]);
}
furi_string_cat_printf(str, "\n");
}
void nfc_render_emv_aid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) {
if(uid_len == 0) return;
furi_string_cat_printf(str, "UID: ");
for(uint8_t i = 0; i < uid_len; i++) {
furi_string_cat_printf(str, "%02X ", uid[i]);
}
furi_string_cat_printf(str, "\n");
}
void nfc_render_emv_data(const EmvData* data, FuriString* str) {
nfc_render_emv_pan(data->emv_application.pan, data->emv_application.pan_len, str);
nfc_render_emv_name(data->emv_application.name, str);
}
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str) {
if(len == 0) return;
FuriString* card_number = furi_string_alloc();
for(uint8_t i = 0; i < len; i++) {
if((i % 2 == 0) && (i != 0)) furi_string_cat_printf(card_number, " ");
furi_string_cat_printf(card_number, "%02X", data[i]);
}
// Cut padding 'F' from card number
furi_string_trim(card_number, "F");
furi_string_cat(str, card_number);
furi_string_free(card_number);
furi_string_cat_printf(str, "\n");
}
void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str) {
if(apl->exp_month == 0) return;
furi_string_cat_printf(str, "Exp: %02X/%02X\n", apl->exp_month, apl->exp_year);
}
void nfc_render_emv_currency(uint16_t cur_code, FuriString* str) {
if(!cur_code) return;
furi_string_cat_printf(str, "Currency code: %04X\n", cur_code);
}
void nfc_render_emv_country(uint16_t country_code, FuriString* str) {
if(!country_code) return;
furi_string_cat_printf(str, "Country code: %04X\n", country_code);
}
void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
const uint8_t len = apl->aid_len;
if(!len) {
furi_string_cat_printf(str, "No Pay Application found\n");
return;
}
furi_string_cat_printf(str, "AID: ");
for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]);
furi_string_cat_printf(str, "\n");
}
static void nfc_render_emv_pin_try_counter(uint8_t counter, FuriString* str) {
if(counter == 0xff) return;
furi_string_cat_printf(str, "PIN attempts left: %d\n", counter);
}
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
if(apl->transaction_counter)
furi_string_cat_printf(str, "Transactions count: %d\n", apl->transaction_counter);
if(apl->last_online_atc)
furi_string_cat_printf(str, "Last Online ATC: %d\n", apl->last_online_atc);
const uint8_t len = apl->active_tr;
if(!len) {
furi_string_cat_printf(str, "No transactions info\n");
return;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* tmp = furi_string_alloc();
furi_string_cat_printf(str, "Transactions:\n");
for(int i = 0; i < len; i++) {
//if(!apl->trans[i].amount) continue; - NO Skip here pls
// transaction counter
furi_string_cat_printf(str, "\e#%d: ", apl->trans[i].atc);
// Print transaction amount
if(!apl->trans[i].amount) {
furi_string_cat_printf(str, "???");
} else {
uint8_t* a = (uint8_t*)&apl->trans[i].amount;
bool top = true;
for(int x = 0; x < 6; x++) {
// cents
if(x == 5) {
furi_string_cat_printf(str, ".%02X", a[x]);
break;
}
if(a[x]) {
if(top) {
furi_string_cat_printf(str, "%X", a[x]);
top = false;
} else {
furi_string_cat_printf(str, "%02X", a[x]);
}
}
}
}
if(apl->trans[i].currency) {
furi_string_set_str(tmp, "UNK");
nfc_emv_parser_get_currency_name(storage, apl->trans[i].currency, tmp);
furi_string_cat_printf(str, " %s\n", furi_string_get_cstr(tmp));
}
if(apl->trans[i].country) {
furi_string_set_str(tmp, "UNK");
nfc_emv_parser_get_country_name(storage, apl->trans[i].country, tmp);
furi_string_cat_printf(str, "Country: %s\n", furi_string_get_cstr(tmp));
}
if(apl->trans[i].date)
furi_string_cat_printf(
str,
"%02lx.%02lx.%02lx ",
apl->trans[i].date >> 16,
(apl->trans[i].date >> 8) & 0xff,
apl->trans[i].date & 0xff);
if(apl->trans[i].time)
furi_string_cat_printf(
str,
"%02lx:%02lx:%02lx\n",
apl->trans[i].time & 0xff,
(apl->trans[i].time >> 8) & 0xff,
apl->trans[i].time >> 16);
}
furi_string_free(tmp);
furi_record_close(RECORD_STORAGE);
}
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
nfc_render_emv_application(&data->emv_application, str);
nfc_render_emv_currency(data->emv_application.currency_code, str);
nfc_render_emv_country(data->emv_application.country_code, str);
nfc_render_emv_pin_try_counter(data->emv_application.pin_try_counter, str);
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <nfc/protocols/emv/emv.h>
#include "../nfc_protocol_support_render_common.h"
#include <stdint.h>
void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str);
void nfc_render_emv_data(const EmvData* data, FuriString* str);
void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str);
void nfc_render_emv_name(const char* data, FuriString* str);
void nfc_render_emv_application(const EmvApplication* data, FuriString* str);
void nfc_render_emv_extra(const EmvData* data, FuriString* str);
void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str);
void nfc_render_emv_country(uint16_t country_code, FuriString* str);
void nfc_render_emv_currency(uint16_t cur_code, FuriString* str);
void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str);
void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str);
void nfc_render_emv_header(FuriString* str);

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -58,8 +59,8 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}

View File

@@ -13,6 +13,8 @@ static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) {
const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -95,8 +97,8 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}

View File

@@ -6,13 +6,17 @@ void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const d
}
}
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str) {
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-A)\n", iso_type);
}
void nfc_render_iso14443_3a_info(
const Iso14443_3aData* data,
NfcProtocolFormatType format_type,
FuriString* str) {
if(format_type == NfcProtocolFormatTypeFull) {
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type);
nfc_render_iso14443_tech_type(data, str);
}
nfc_render_iso14443_3a_brief(data, str);
@@ -30,5 +34,5 @@ void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str)
void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) {
furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]);
furi_string_cat_printf(str, "SAK: %02X", data->sak);
furi_string_cat_printf(str, "\nSAK: %02X", data->sak);
}

View File

@@ -9,6 +9,8 @@ void nfc_render_iso14443_3a_info(
NfcProtocolFormatType format_type,
FuriString* str);
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str);
void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size);
void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str);

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) {
const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -59,8 +60,8 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) {
furi_string_free(temp_str);
}
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -68,7 +69,7 @@ bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}

View File

@@ -4,4 +4,4 @@
#include "iso14443_3b.h"
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event);
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event);

View File

@@ -6,7 +6,7 @@ void nfc_render_iso14443_3b_info(
FuriString* str) {
if(format_type == NfcProtocolFormatTypeFull) {
const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3';
furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type);
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-B)\n", iso_type);
}
furi_string_cat_printf(str, "UID:");
@@ -20,7 +20,7 @@ void nfc_render_iso14443_3b_info(
if(format_type != NfcProtocolFormatTypeFull) return;
furi_string_cat_printf(str, "\n\e#Protocol info\n");
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) {
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
@@ -68,7 +68,7 @@ void nfc_render_iso14443_3b_info(
iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not ";
furi_string_cat_printf(str, "CID: %ssupported", cid_support_str);
furi_string_cat_printf(str, "\n\e#Application data\nRaw:");
furi_string_cat_printf(str, "\n::::::::::::[Application data]::::::::::::\nRaw:");
size_t app_data_size;
const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size);

View File

@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) {
const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -99,8 +100,8 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}

View File

@@ -14,11 +14,12 @@ void nfc_render_iso14443_4a_info(
}
void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) {
nfc_render_iso14443_tech_type(iso14443_4a_get_base_data(data), str);
nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str);
}
void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) {
furi_string_cat_printf(str, "\n\e#Protocol info\n");
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) {
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
@@ -72,7 +73,7 @@ void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str)
const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count);
if(hist_bytes_count > 0) {
furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:");
furi_string_cat_printf(str, "\n:::::::::::::[Historical bytes]:::::::::::::\nRaw:");
for(size_t i = 0; i < hist_bytes_count; ++i) {
furi_string_cat_printf(str, " %02X", hist_bytes[i]);

View File

@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) {
const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -64,8 +65,8 @@ static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) {
UNUSED(instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEmulate) {
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}
@@ -73,7 +74,7 @@ static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}

View File

@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) {
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_reset(instance->widget);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_iso15693_3(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
FuriString* temp_str = furi_string_alloc();
nfc_render_iso15693_3_system_info(data, temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso15693_3);
@@ -94,8 +108,8 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
}
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -104,13 +118,19 @@ static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t
}
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_iso15693_3,

View File

@@ -18,50 +18,25 @@ void nfc_render_iso15693_3_info(
}
void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
furi_string_cat_printf(str, "UID:");
furi_string_cat_printf(str, "UID:\n");
size_t uid_len;
const uint8_t* uid = iso15693_3_get_uid(data, &uid_len);
for(size_t i = 0; i < uid_len; i++) {
furi_string_cat_printf(str, " %02X", uid[i]);
furi_string_cat_printf(str, "%02X ", uid[i]);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
const uint16_t block_count = iso15693_3_get_block_count(data);
const uint8_t block_size = iso15693_3_get_block_size(data);
furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size);
furi_string_cat_printf(str, "\nMemory: %u bytes\n", block_count * block_size);
furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size);
}
}
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
furi_string_cat(str, "\n\e#General info\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
}
furi_string_cat(str, "\e#Lock bits\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) {
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
@@ -88,5 +63,34 @@ void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
"(Data is too big. Showing only the first %u bytes.)",
display_block_count * block_size);
}
} else {
furi_string_cat(str, "\e#No available data\n");
}
}
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
furi_string_cat(str, "\n::::::::::::::::[General info]:::::::::::::::::\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
}
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
furi_string_cat_printf(
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
furi_string_cat_printf(
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
}
}

View File

@@ -12,3 +12,5 @@ void nfc_render_iso15693_3_info(
void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str);
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str);
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str);

View File

@@ -21,8 +21,11 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_classic_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_add_text_scroll_element(
@@ -97,8 +100,9 @@ static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance);
}
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) {
if(event == NfcCustomEventPollerIncomplete) {
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom &&
event.event == NfcCustomEventPollerIncomplete) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
}
@@ -119,13 +123,15 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
}
}
static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) {
static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { //-V524
const NfcDevice* device = instance->nfc_device;
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
FuriString* temp_str = furi_string_alloc();
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_classic_info(data, NfcProtocolFormatTypeShort, temp_str);
widget_add_text_scroll_element(
@@ -166,9 +172,9 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) {
nfc_listener_start(instance->listener, NULL, NULL);
}
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
dolphin_deed(DolphinDeedNfcDetectReader);
return true;
}
@@ -176,27 +182,29 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t e
return false;
}
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else if(event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
consumed = true;
} else if(event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
consumed = true;
} else if(event.event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
}
}
return consumed;
}
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) {
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event == NfcCustomEventTextInputDone) {
if(event.type == SceneManagerEventTypeCustom && event.event == NfcCustomEventTextInputDone) {
mf_classic_key_cache_save(
instance->mfc_key_cache,
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic));

View File

@@ -18,13 +18,48 @@ void nfc_render_mf_classic_info(
furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total);
}
static void
mf_classic_render_raw_data(const uint8_t* data, size_t size, bool data_read, FuriString* str) {
furi_assert((size % 2) == 0);
for(size_t i = 0; i < size; i += 2) {
if(data_read) {
furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]);
} else {
furi_string_cat_printf(str, "???? ");
}
}
}
static void
mf_classic_render_block(const MfClassicData* data, uint8_t block_num, FuriString* str) {
if(mf_classic_is_sector_trailer(block_num)) {
uint8_t sec_num = mf_classic_get_sector_by_block(block_num);
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
// Render key A
bool key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeA);
mf_classic_render_raw_data(sec_tr->key_a.data, sizeof(MfClassicKey), key_read, str);
// Render access bits
bool access_bits_read = mf_classic_is_block_read(data, block_num);
mf_classic_render_raw_data(
sec_tr->access_bits.data, sizeof(MfClassicAccessBits), access_bits_read, str);
// Render key B
key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeB);
mf_classic_render_raw_data(sec_tr->key_b.data, sizeof(MfClassicKey), key_read, str);
} else {
const uint8_t* block_data = data->block[block_num].data;
bool block_read = mf_classic_is_block_read(data, block_num);
mf_classic_render_raw_data(block_data, sizeof(MfClassicBlock), block_read, str);
}
}
void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) {
uint16_t total_blocks = mf_classic_get_total_block_num(data->type);
for(size_t i = 0; i < total_blocks; i++) {
for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) {
furi_string_cat_printf(
str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]);
}
mf_classic_render_block(data, i, str);
}
}

View File

@@ -14,8 +14,10 @@ static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) {
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_add_text_scroll_element(
@@ -56,6 +58,7 @@ static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) {
FuriString* temp_str = furi_string_alloc();
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
furi_string_replace(temp_str, "Mifare", "MIFARE");
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str);
widget_add_text_scroll_element(

View File

@@ -2,6 +2,7 @@
#include "mf_ultralight_render.h"
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
#include <toolbox/pretty_format.h>
#include "nfc/nfc_app_i.h"
@@ -15,11 +16,18 @@ enum {
SubmenuIndexWrite,
};
enum {
NfcSceneMoreInfoStateASCII,
NfcSceneMoreInfoStateRawData,
};
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -35,11 +43,62 @@ static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) {
const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight);
furi_string_reset(instance->text_box_store);
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
uint32_t scene_state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo);
text_box_set_font(instance->text_box, TextBoxFontHex);
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
if(scene_state == NfcSceneMoreInfoStateASCII) {
pretty_format_bytes_hex_canonical(
instance->text_box_store,
MF_ULTRALIGHT_PAGE_SIZE,
PRETTY_FORMAT_FONT_MONOSPACE,
(uint8_t*)mfu->page,
mfu->pages_read * MF_ULTRALIGHT_PAGE_SIZE);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
widget_add_button_element(
instance->widget,
GuiButtonTypeRight,
"Raw Data",
nfc_protocol_support_common_widget_callback,
instance);
widget_add_button_element(
instance->widget,
GuiButtonTypeLeft,
"Info",
nfc_protocol_support_common_widget_callback,
instance);
} else if(scene_state == NfcSceneMoreInfoStateRawData) {
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
widget_add_button_element(
instance->widget,
GuiButtonTypeLeft,
"ASCII",
nfc_protocol_support_common_widget_callback,
instance);
}
}
static bool nfc_scene_more_info_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) ||
(event.type == SceneManagerEventTypeBack)) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII);
scene_manager_previous_scene(instance->scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData);
scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo);
consumed = true;
}
return consumed;
}
static NfcCommand
@@ -132,15 +191,17 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance);
}
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
if(event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
nfc_scene_read_setup_view(instance);
} else if((event == NfcCustomEventPollerIncomplete)) {
notification_message(instance->notifications, &sequence_semi_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
nfc_scene_read_setup_view(instance);
} else if((event.event == NfcCustomEventPollerIncomplete)) {
notification_message(instance->notifications, &sequence_semi_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
}
}
return true;
}
@@ -202,14 +263,17 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) {
nfc_listener_start(instance->listener, NULL, NULL);
}
static bool
nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
return true;
} else if(event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
return true;
static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
NfcApp* instance,
SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
return true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
return true;
}
}
return false;
}
@@ -225,7 +289,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_mf_ultralight,
.on_event = nfc_protocol_support_common_on_event_empty,
.on_event = nfc_scene_more_info_on_event_mf_ultralight,
},
.scene_read =
{

View File

@@ -36,10 +36,11 @@ void nfc_render_mf_ultralight_info(
}
void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) {
furi_string_cat_printf(str, "\e*");
for(size_t i = 0; i < data->pages_read; i++) {
const uint8_t* page_data = data->page[i].data;
for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) {
furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]);
furi_string_cat_printf(str, " %02X%02X", page_data[j], page_data[j + 1]);
}
}
}

View File

@@ -74,7 +74,7 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context)
nfc_protocol_support_scenes[scene].on_exit(instance);
}
static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
return nfc_protocol_support[protocol]->features & feature;
}
@@ -131,16 +131,15 @@ static bool
nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event);
}
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event);
return consumed;
}
static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) {
text_box_reset(instance->text_box);
widget_reset(instance->widget);
furi_string_reset(instance->text_box_store);
}
@@ -188,8 +187,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
} else {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed =
nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.event == NfcCustomEventPollerFailure) {
nfc_poller_stop(instance->poller);
@@ -202,7 +200,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
} else if(event.event == NfcCustomEventCardDetected) {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
nfc_poller_stop(instance->poller);
@@ -287,8 +285,7 @@ static bool
consumed = true;
} else {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed =
nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
@@ -391,12 +388,15 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) {
nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance);
// Trailer submenu items
submenu_add_item(
submenu,
"Info",
SubmenuIndexCommonInfo,
nfc_protocol_support_common_submenu_callback,
instance);
if(nfc_has_shadow_file(instance)) {
submenu_add_item(
submenu,
"Restore to Original State",
SubmenuIndexCommonRestore,
nfc_protocol_support_common_submenu_callback,
instance);
}
submenu_add_item(
submenu,
"Rename",
@@ -409,15 +409,12 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) {
SubmenuIndexCommonDelete,
nfc_protocol_support_common_submenu_callback,
instance);
if(nfc_has_shadow_file(instance)) {
submenu_add_item(
submenu,
"Restore Data Changes",
SubmenuIndexCommonRestore,
nfc_protocol_support_common_submenu_callback,
instance);
}
submenu_add_item(
submenu,
"Info",
SubmenuIndexCommonInfo,
nfc_protocol_support_common_submenu_callback,
instance);
submenu_set_selected_item(
instance->submenu,
@@ -456,8 +453,7 @@ static bool
consumed = true;
} else {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed =
nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event);
consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {
@@ -523,8 +519,8 @@ static bool
DolphinDeedNfcSave);
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
consumed = nfc_protocol_support[protocol]->scene_save_name.on_event(
instance, event.event);
consumed =
nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event);
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneStart);
@@ -565,11 +561,11 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
FuriString* temp_str = furi_string_alloc();
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64);
if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
widget_add_string_element(
widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID");
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID");
size_t uid_len;
const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
@@ -581,14 +577,25 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
furi_string_trim(temp_str);
} else {
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
furi_string_set(
temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
furi_string_cat_printf(temp_str, "\n%s", furi_string_get_cstr(instance->file_name));
widget_add_string_element(
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating");
if(!furi_string_empty(instance->file_name)) {
furi_string_printf(
temp_str,
"%s\n%s",
nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull),
furi_string_get_cstr(instance->file_name));
} else {
furi_string_printf(
temp_str,
"Unsaved\n%s",
nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
}
}
widget_add_text_box_element(
widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
widget, 56, 33, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
furi_string_free(temp_str);

View File

@@ -76,6 +76,7 @@
#pragma once
#include <gui/scene_manager.h>
#include <lib/nfc/protocols/nfc_protocol.h>
#include "nfc_protocol_support_common.h"
@@ -111,3 +112,5 @@ bool nfc_protocol_support_on_event(
* @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
*/
void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context);
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature);

View File

@@ -7,6 +7,7 @@
#include <core/string.h>
#include "../../nfc_app.h"
#include "../../nfc_app_i.h"
/**
* @brief Scene entry handler.
@@ -19,10 +20,10 @@ typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance);
* @brief Scene event handler.
*
* @param[in,out] instance pointer to the NFC application instance.
* @param[in] event custom event that has occurred.
* @param[in] event scene manager event that has occurred.
* @returns true if the event was handled, false otherwise.
*/
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event);
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, SceneManagerEvent event);
/**
* @brief Abstract scene interface.

View File

@@ -18,6 +18,7 @@
#include "mf_ultralight/mf_ultralight.h"
#include "mf_classic/mf_classic.h"
#include "mf_desfire/mf_desfire.h"
#include "emv/emv.h"
#include "slix/slix.h"
#include "st25tb/st25tb.h"
@@ -41,5 +42,6 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
[NfcProtocolSlix] = &nfc_protocol_support_slix,
[NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
[NfcProtocolEmv] = &nfc_protocol_support_emv,
/* Add new protocol support implementations here */
};

View File

@@ -35,8 +35,8 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) {
UNUSED(instance);
}
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) {
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event) {
UNUSED(instance);
UNUSED(event);
return true;
return event.type != SceneManagerEventTypeBack;
}

View File

@@ -7,6 +7,7 @@
#include <gui/modules/widget.h>
#include "nfc/nfc_app.h"
#include "nfc/nfc_app_i.h"
/**
* @brief Common submenu indices.
@@ -82,4 +83,4 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance);
* @param[in] event custom event type that has occurred.
* @returns always true.
*/
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event);
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event);

View File

@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_reset(instance->widget);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_slix(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
FuriString* temp_str = furi_string_alloc();
nfc_render_iso15693_3_system_info(slix_get_base_data(data), temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolSlix);
@@ -91,8 +105,8 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
}
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
@@ -101,13 +115,18 @@ static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event)
}
const NfcProtocolSupportBase nfc_protocol_support_slix = {
.features = NfcProtocolFeatureEmulateFull,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_slix,

View File

@@ -1,14 +1,12 @@
#include "slix_render.h"
#include "../iso15693_3/iso15693_3_render.h"
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) {
nfc_render_iso15693_3_brief(slix_get_base_data(data), str);
if(format_type != NfcProtocolFormatTypeFull) return;
const SlixType slix_type = slix_get_type(data);
furi_string_cat(str, "\n\e#Passwords\n");
furi_string_cat(str, "\n::::::::::::::::::[Passwords]:::::::::::::::::\n");
static const char* slix_password_names[] = {
"Read",
@@ -25,7 +23,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
}
}
furi_string_cat(str, "\e#Lock bits\n");
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) {
furi_string_cat_printf(
@@ -38,7 +36,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
const SlixProtection protection = data->system_info.protection;
furi_string_cat(str, "\e#Page protection\n");
furi_string_cat(str, "::::::::::::[Page protection]::::::::::::\n");
furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer);
const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un";
@@ -52,12 +50,12 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
}
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) {
furi_string_cat(str, "\e#Privacy\n");
furi_string_cat(str, "::::::::::::::::::::[Privacy]::::::::::::::::::::::\n");
furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis");
}
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) {
furi_string_cat(str, "\e#Signature\n");
furi_string_cat(str, ":::::::::::::::::::[Signature]::::::::::::::::::\n");
for(uint32_t i = 0; i < 4; ++i) {
furi_string_cat_printf(str, "%02X ", data->signature[i]);
}

View File

@@ -3,5 +3,6 @@
#include <nfc/protocols/slix/slix.h>
#include "../nfc_protocol_support_render_common.h"
#include "../iso15693_3/iso15693_3_render.h"
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str);

View File

@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) {
const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
FuriString* temp_str = furi_string_alloc();
nfc_append_filename_string_when_present(instance, temp_str);
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str);
@@ -60,8 +61,8 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) {
if(event == SubmenuIndexCommonEdit) {
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}

View File

@@ -1,4 +1,5 @@
#include "nfc_app_i.h"
#include "helpers/protocol_support/nfc_protocol_support.h"
#include <dolphin/dolphin.h>
@@ -445,6 +446,15 @@ void nfc_app_reset_detected_protocols(NfcApp* instance) {
instance->protocols_detected_num = 0;
}
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) {
furi_assert(instance);
furi_assert(string);
if(!furi_string_empty(instance->file_name)) {
furi_string_cat_printf(string, "Name:%s\n", furi_string_get_cstr(instance->file_name));
}
}
static bool nfc_is_hal_ready() {
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
// No connection to the chip, show an error screen
@@ -466,6 +476,15 @@ static bool nfc_is_hal_ready() {
}
}
static void nfc_show_initial_scene_for_device(NfcApp* nfc) {
NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device);
uint32_t scene = nfc_protocol_support_has_feature(
prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ?
NfcSceneEmulate :
NfcSceneSavedMenu;
scene_manager_next_scene(nfc->scene_manager, scene);
}
int32_t nfc_app(void* p) {
if(!nfc_is_hal_ready()) return 0;
@@ -485,7 +504,7 @@ int32_t nfc_app(void* p) {
furi_string_set(nfc->file_path, args);
if(nfc_load_file(nfc, nfc->file_path, false)) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate);
nfc_show_initial_scene_for_device(nfc);
} else {
view_dispatcher_stop(nfc->view_dispatcher);
}

View File

@@ -30,6 +30,7 @@
#include "helpers/mf_ultralight_auth.h"
#include "helpers/mf_user_dict.h"
#include "helpers/mfkey32_logger.h"
#include "helpers/nfc_emv_parser.h"
#include "helpers/mf_classic_key_cache.h"
#include "helpers/nfc_supported_cards.h"
@@ -192,3 +193,5 @@ void nfc_make_app_folder(NfcApp* instance);
void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
void nfc_app_reset_detected_protocols(NfcApp* instance);
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);

View File

@@ -0,0 +1,134 @@
/*
* Parser for EMV cards.
*
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/string.h"
#include "furi_hal_rtc.h"
#include "helpers/nfc_emv_parser.h"
#include "nfc_supported_card_plugin.h"
#include "protocols/emv/emv.h"
#include "protocols/nfc_protocol.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#define TAG "EMV"
bool emv_get_currency_name(uint16_t cur_code, FuriString* currency_name) {
if(!cur_code) return false;
Storage* storage = furi_record_open(RECORD_STORAGE);
bool succsess = nfc_emv_parser_get_currency_name(storage, cur_code, currency_name);
furi_record_close(RECORD_STORAGE);
return succsess;
}
bool emv_get_country_name(uint16_t country_code, FuriString* country_name) {
if(!country_code) return false;
Storage* storage = furi_record_open(RECORD_STORAGE);
bool succsess = nfc_emv_parser_get_country_name(storage, country_code, country_name);
furi_record_close(RECORD_STORAGE);
return succsess;
}
bool emv_get_aid_name(const EmvApplication* apl, FuriString* aid_name) {
const uint8_t len = apl->aid_len;
if(!len) return false;
Storage* storage = furi_record_open(RECORD_STORAGE);
bool succsess = nfc_emv_parser_get_aid_name(storage, apl->aid, len, aid_name);
furi_record_close(RECORD_STORAGE);
return succsess;
}
static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
bool parsed = false;
const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv);
const EmvApplication app = data->emv_application;
do {
if(app.name_found)
furi_string_cat_printf(parsed_data, "\e#%s\n", app.name);
else
furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV");
if(app.pan_len) {
FuriString* pan = furi_string_alloc();
for(uint8_t i = 0; i < app.pan_len; i += 2) {
furi_string_cat_printf(pan, "%02X%02X ", app.pan[i], app.pan[i + 1]);
}
// Cut padding 'F' from card number
size_t end = furi_string_search_rchar(pan, 'F');
if(end) furi_string_left(pan, end);
furi_string_cat(parsed_data, pan);
furi_string_free(pan);
}
if(app.exp_month | app.exp_year)
furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X\n", app.exp_month, app.exp_year);
FuriString* str = furi_string_alloc();
bool storage_readed = emv_get_country_name(app.country_code, str);
if(storage_readed)
furi_string_cat_printf(parsed_data, "Country: %s\n", furi_string_get_cstr(str));
storage_readed = emv_get_currency_name(app.currency_code, str);
if(storage_readed)
furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str));
if(app.pin_try_counter != 0xFF)
furi_string_cat_printf(parsed_data, "PIN attempts left: %d\n", app.pin_try_counter);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin emv_plugin = {
.protocol = NfcProtocolEmv,
.verify = NULL,
.read = NULL,
.parse = emv_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor emv_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &emv_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* emv_plugin_ep() {
return &emv_plugin_descriptor;
}

View File

@@ -328,6 +328,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
last_trip.minute);
}
furi_string_free(tariff_name);
parsed = true;
} while(false);

View File

@@ -147,7 +147,7 @@ static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) {
const uint8_t* block_start_ptr =
&data->block[start_block_num + ticket_block_number].data[0];
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4) - 100;
uint32_t balance_lari = balance / 100;
uint8_t balance_tetri = balance % 100;

View File

@@ -0,0 +1,475 @@
// Parser for NDEF format data
// Supports multiple NDEF messages and records in same tag
// Parsed types: URI (+ Phone, Mail), Text, BT MAC, Contact, WiFi, Empty
// Documentation and sources indicated where relevant
// Made by @Willy-JL
#include "nfc_supported_card_plugin.h"
#include <nfc/helpers/nfc_util.h>
#include <flipper_application/flipper_application.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#define TAG "NDEF"
static bool is_text(const uint8_t* buf, size_t len) {
for(size_t i = 0; i < len; i++) {
const char c = buf[i];
if((c < ' ' || c > '~') && c != '\r' && c != '\n') {
return false;
}
}
return true;
}
static void
print_data(FuriString* str, const char* prefix, const uint8_t* buf, size_t len, bool force_hex) {
if(prefix) furi_string_cat_printf(str, "%s: ", prefix);
if(!force_hex && is_text(buf, len)) {
char* tmp = malloc(len + 1);
memcpy(tmp, buf, len);
tmp[len] = '\0';
furi_string_cat_printf(str, "%s", tmp);
free(tmp);
} else {
for(uint8_t i = 0; i < len; i++) {
furi_string_cat_printf(str, "%02X ", buf[i]);
}
}
furi_string_cat(str, "\n");
}
static void parse_ndef_uri(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
// https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#uri-records-0x55-slash-u-607763
const char* prepends[] = {
[0x00] = "",
[0x01] = "http://www.",
[0x02] = "https://www.",
[0x03] = "http://",
[0x04] = "https://",
[0x05] = "tel:",
[0x06] = "mailto:",
[0x07] = "ftp://anonymous:anonymous@",
[0x08] = "ftp://ftp.",
[0x09] = "ftps://",
[0x0A] = "sftp://",
[0x0B] = "smb://",
[0x0C] = "nfs://",
[0x0D] = "ftp://",
[0x0E] = "dav://",
[0x0F] = "news:",
[0x10] = "telnet://",
[0x11] = "imap:",
[0x12] = "rtsp://",
[0x13] = "urn:",
[0x14] = "pop:",
[0x15] = "sip:",
[0x16] = "sips:",
[0x17] = "tftp:",
[0x18] = "btspp://",
[0x19] = "btl2cap://",
[0x1A] = "btgoep://",
[0x1B] = "tcpobex://",
[0x1C] = "irdaobex://",
[0x1D] = "file://",
[0x1E] = "urn:epc:id:",
[0x1F] = "urn:epc:tag:",
[0x20] = "urn:epc:pat:",
[0x21] = "urn:epc:raw:",
[0x22] = "urn:epc:",
[0x23] = "urn:nfc:",
};
const char* prepend = "";
uint8_t prepend_type = payload[0];
if(prepend_type < COUNT_OF(prepends)) {
prepend = prepends[prepend_type];
}
size_t prepend_len = strlen(prepend);
size_t uri_len = prepend_len + (payload_len - 1);
char* const uri_buf = malloc(uri_len);
memcpy(uri_buf, prepend, prepend_len);
memcpy(uri_buf + prepend_len, payload + 1, payload_len - 1);
char* uri = uri_buf;
const char* type = "URI";
if(strncmp(uri, "http", strlen("http")) == 0) {
type = "URL";
} else if(strncmp(uri, "tel:", strlen("tel:")) == 0) {
type = "Phone";
uri += strlen("tel:");
uri_len -= strlen("tel:");
} else if(strncmp(uri, "mailto:", strlen("mailto:")) == 0) {
type = "Mail";
uri += strlen("mailto:");
uri_len -= strlen("mailto:");
}
furi_string_cat_printf(str, "%s\n", type);
print_data(str, NULL, (uint8_t*)uri, uri_len, false);
free(uri_buf);
}
static void parse_ndef_text(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
furi_string_cat(str, "Text\n");
print_data(str, NULL, payload + 3, payload_len - 3, false);
}
static void parse_ndef_bt(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
furi_string_cat(str, "BT MAC\n");
print_data(str, NULL, payload + 2, payload_len - 2, true);
}
static void parse_ndef_vcard(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
char* tmp = malloc(payload_len + 1);
memcpy(tmp, payload, payload_len);
tmp[payload_len] = '\0';
FuriString* fmt = furi_string_alloc_set(tmp);
free(tmp);
furi_string_trim(fmt);
if(furi_string_start_with(fmt, "BEGIN:VCARD")) {
furi_string_right(fmt, furi_string_search_char(fmt, '\n'));
if(furi_string_end_with(fmt, "END:VCARD")) {
furi_string_left(fmt, furi_string_search_rchar(fmt, '\n'));
}
furi_string_trim(fmt);
if(furi_string_start_with(fmt, "VERSION:")) {
furi_string_right(fmt, furi_string_search_char(fmt, '\n'));
furi_string_trim(fmt);
}
}
furi_string_cat(str, "Contact\n");
print_data(str, NULL, (uint8_t*)furi_string_get_cstr(fmt), furi_string_size(fmt), false);
furi_string_free(fmt);
}
static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t payload_len) {
// https://android.googlesource.com/platform/packages/apps/Nfc/+/refs/heads/main/src/com/android/nfc/NfcWifiProtectedSetup.java
#define CREDENTIAL_FIELD_ID (0x100E)
#define SSID_FIELD_ID (0x1045)
#define NETWORK_KEY_FIELD_ID (0x1027)
#define AUTH_TYPE_FIELD_ID (0x1003)
#define AUTH_TYPE_EXPECTED_SIZE (2)
#define AUTH_TYPE_OPEN (0x0001)
#define AUTH_TYPE_WPA_PSK (0x0002)
#define AUTH_TYPE_WPA_EAP (0x0008)
#define AUTH_TYPE_WPA2_EAP (0x0010)
#define AUTH_TYPE_WPA2_PSK (0x0020)
#define AUTH_TYPE_WPA_AND_WPA2_PSK (0x0022)
#define MAX_NETWORK_KEY_SIZE_BYTES (64)
size_t i = 0;
while(i < payload_len) {
uint16_t field_id = nfc_util_bytes2num(payload + i, 2);
i += 2;
uint16_t field_len = nfc_util_bytes2num(payload + i, 2);
i += 2;
if(field_id == CREDENTIAL_FIELD_ID) {
furi_string_cat(str, "WiFi\n");
size_t start_position = i;
while(i < start_position + field_len) {
uint16_t cfg_id = nfc_util_bytes2num(payload + i, 2);
i += 2;
uint16_t cfg_len = nfc_util_bytes2num(payload + i, 2);
i += 2;
if(i + cfg_len > start_position + field_len) {
return;
}
switch(cfg_id) {
case SSID_FIELD_ID:
print_data(str, "SSID", payload + i, cfg_len, false);
i += cfg_len;
break;
case NETWORK_KEY_FIELD_ID:
if(cfg_len > MAX_NETWORK_KEY_SIZE_BYTES) {
return;
}
print_data(str, "PWD", payload + i, cfg_len, false);
i += cfg_len;
break;
case AUTH_TYPE_FIELD_ID:
if(cfg_len != AUTH_TYPE_EXPECTED_SIZE) {
return;
}
short auth_type = nfc_util_bytes2num(payload + i, 2);
i += 2;
const char* auth;
switch(auth_type) {
case AUTH_TYPE_OPEN:
auth = "Open";
break;
case AUTH_TYPE_WPA_PSK:
auth = "WPA Personal";
break;
case AUTH_TYPE_WPA_EAP:
auth = "WPA Enterprise";
break;
case AUTH_TYPE_WPA2_EAP:
auth = "WPA2 Enterprise";
break;
case AUTH_TYPE_WPA2_PSK:
auth = "WPA2 Personal";
break;
case AUTH_TYPE_WPA_AND_WPA2_PSK:
auth = "WPA/WPA2 Personal";
break;
default:
auth = "Unknown";
break;
}
print_data(str, "AUTH", (uint8_t*)auth, strlen(auth), false);
break;
default:
i += cfg_len;
break;
}
}
return;
}
i += field_len;
}
}
static void parse_ndef_payload(
FuriString* str,
uint8_t tnf,
const char* type,
uint8_t type_len,
const uint8_t* payload,
uint32_t payload_len) {
if(!payload_len) {
furi_string_cat(str, "Empty\n");
return;
}
switch(tnf) {
case 0x01: // NFC Forum well-known type [NFC RTD]
if(strncmp("U", type, type_len) == 0) {
parse_ndef_uri(str, payload, payload_len);
} else if(strncmp("T", type, type_len) == 0) {
parse_ndef_text(str, payload, payload_len);
} else {
print_data(str, "Well-known type", (uint8_t*)type, type_len, false);
print_data(str, "Payload", payload, payload_len, false);
}
break;
case 0x02: // Media-type [RFC 2046]
if(strncmp("application/vnd.bluetooth.ep.oob", type, type_len) == 0) {
parse_ndef_bt(str, payload, payload_len);
} else if(strncmp("text/vcard", type, type_len) == 0) {
parse_ndef_vcard(str, payload, payload_len);
} else if(strncmp("application/vnd.wfa.wsc", type, type_len) == 0) {
parse_ndef_wifi(str, payload, payload_len);
} else {
print_data(str, "Media Type", (uint8_t*)type, type_len, false);
print_data(str, "Payload", payload, payload_len, false);
}
break;
case 0x00: // Empty
case 0x03: // Absolute URI [RFC 3986]
case 0x04: // NFC Forum external type [NFC RTD]
case 0x05: // Unknown
case 0x06: // Unchanged
case 0x07: // Reserved
default: // Unknown
// Dump data without parsing
print_data(str, "Type name format", &tnf, 1, true);
print_data(str, "Type", (uint8_t*)type, type_len, false);
print_data(str, "Payload", payload, payload_len, false);
break;
}
}
static const uint8_t* parse_ndef_message(
FuriString* str,
size_t message_num,
const uint8_t* cur,
const uint8_t* message_end) {
// NDEF message and record documentation:
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#ndef-message-and-record-format
size_t record_num = 0;
bool last_record = false;
while(cur < message_end) {
// Flags and TNF
uint8_t flags_tnf = *cur++;
// Message Begin should only be set on first record
if(record_num++ && flags_tnf & (1 << 7)) break;
// Message End should only be set on last record
if(last_record) break;
if(flags_tnf & (1 << 6)) last_record = true;
// Chunked Flag not supported
if(flags_tnf & (1 << 5)) break;
// Payload Length field of 1 vs 4 bytes
bool short_record = flags_tnf & (1 << 4);
// Is payload ID length and value present
bool id_present = flags_tnf & (1 << 3);
// Type Name Format 3 bit value
uint8_t tnf = flags_tnf & 0b00000111;
// Type Length
uint8_t type_len = *cur++;
// Payload Length
uint32_t payload_len;
if(short_record) {
payload_len = *cur++;
} else {
payload_len = nfc_util_bytes2num(cur, 4);
cur += 4;
}
// ID Length
uint8_t id_len = 0;
if(id_present) {
id_len = *cur++;
}
// Payload Type
char* type = NULL;
if(type_len) {
type = malloc(type_len);
memcpy(type, cur, type_len);
cur += type_len;
}
// Payload ID
cur += id_len;
furi_string_cat_printf(str, "\e*> M:%d R:%d - ", message_num, record_num);
parse_ndef_payload(str, tnf, type, type_len, cur, payload_len);
cur += payload_len;
free(type);
furi_string_trim(str, "\n");
furi_string_cat(str, "\n\n");
}
return cur;
}
static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
furi_assert(parsed_data);
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
bool parsed = false;
do {
// 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; // ...
// Data content starts here at 5th page
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);
size_t message_num = 0;
// Parse as TLV (see docs above)
while(cur < end) {
switch(*cur++) {
case 0x03: { // NDEF message
if(cur >= end) break;
uint16_t len;
if(*cur < 0xFF) { // 1 byte length
len = *cur++;
} else { // 3 byte length (0xFF marker + 2 byte integer)
if(cur + 2 >= end) {
cur = end;
break;
}
len = nfc_util_bytes2num(++cur, 2);
cur += 2;
}
if(cur + len >= end) {
cur = end;
break;
}
if(message_num++ == 0) {
furi_string_printf(
parsed_data,
"\e#NDEF Format Data\nCard type: %s\n",
mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull));
}
const uint8_t* message_end = cur + len;
cur = parse_ndef_message(parsed_data, message_num, cur, message_end);
if(cur != message_end) cur = end;
break;
}
case 0xFE: // TLV end
cur = end;
if(message_num != 0) parsed = true;
break;
case 0x00: // Padding, has no length, skip
break;
case 0x01: // Lock control
case 0x02: // Memory control
case 0xFD: // Proprietary
// We don't care, skip this TLV block
if(cur >= end) break;
if(*cur < 0xFF) { // 1 byte length
cur += *cur + 1; // Shift by TLV length
} else { // 3 byte length (0xFF marker + 2 byte integer)
if(cur + 2 >= end) {
cur = end;
break;
}
cur += nfc_util_bytes2num(cur + 1, 2) + 3; // Shift by TLV length
}
break;
default: // Unknown, bail to avoid problems
cur = end;
break;
}
}
if(parsed) {
furi_string_trim(parsed_data, "\n");
furi_string_cat(parsed_data, "\n");
} else {
furi_string_reset(parsed_data);
}
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin ndef_plugin = {
.protocol = NfcProtocolMfUltralight,
.verify = NULL,
.read = NULL,
.parse = ndef_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor ndef_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &ndef_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* ndef_plugin_ep() {
return &ndef_plugin_descriptor;
}

View File

@@ -161,7 +161,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_string_printf(
parsed_data,
"\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u USD",
"\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u EUR",
uid_len * 2,
card_number,
balance_usd,

View File

@@ -34,11 +34,6 @@
#define PURSE_SECTOR_NUM (6)
#define INFO_SECTOR_NUM (15)
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
// Sector 15 data. Byte [11] contains the mistake. If byte [11] was 0xEF, bytes [1-18] means "ЗАО Золотая Корона"
static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x87, 0xAE,
0xAB, 0xAE, 0xF2, 0xA0, 0xEF, 0x20, 0x8A,
@@ -76,19 +71,24 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE;
}
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes) {
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
furi_assert(src);
furi_assert(len_bytes <= 9);
uint64_t res = 0;
uint64_t result = 0;
*is_bcd = true;
for(uint8_t i = 0; i < len_bytes; i++) {
res *= 10;
res += src[i] / 16;
res *= 10;
res += src[i] % 16;
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
result *= 10;
result += src[i] / 16;
result *= 10;
result += src[i] % 16;
}
return res;
return result;
}
static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_data) {
@@ -121,12 +121,12 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
// INFO SECTOR
// block 1
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1);
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1, &verified);
// block 2
block_start_ptr = &data->block[start_info_block_number + 2].data[4];
const uint64_t card_number =
bytes2num_bcd(block_start_ptr, 9) * 10 + bytes2num_bcd(block_start_ptr + 9, 1) / 10;
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &verified);
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &verified) / 10;
// TRIP SECTOR
const uint8_t start_trip_block_number =
@@ -157,7 +157,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
block_start_ptr = &data->block[start_trip_block_number + 2].data[0];
const char validator_first_letter =
nfc_util_bytes2num_little_endian(block_start_ptr + 1, 1);
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3);
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3, &verified);
const uint32_t last_trip_timestamp =
nfc_util_bytes2num_little_endian(block_start_ptr + 6, 4);
const uint8_t track_number = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 1);
@@ -174,15 +174,16 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
block_start_ptr = &data->block[start_purse_block_number].data[0];
// block 0
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
const uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
uint32_t balance_rub = balance / 100;
uint8_t balance_kop = balance % 100;
furi_string_cat_printf(
parsed_data,
"\e#Zolotaya korona\nCard number: %llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
card_number,
"\e#Zolotaya korona\nCard number: %u%015llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
card_number_prefix,
card_number_postfix,
region_number,
balance_rub,
balance_kop,

View File

@@ -0,0 +1,166 @@
/*
* Parser for Zolotaya Korona Online card (Russia).
* Tariffs research by DNZ1393
*
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "furi_hal_rtc.h"
#include "nfc_supported_card_plugin.h"
#include "protocols/mf_classic/mf_classic.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#define TAG "Zolotaya Korona Online"
#define TRIP_SECTOR_NUM (4)
#define INFO_SECTOR_NUM (15)
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
furi_assert(src);
furi_assert(len_bytes <= 9);
uint64_t result = 0;
*is_bcd = true;
for(uint8_t i = 0; i < len_bytes; i++) {
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
result *= 10;
result += src[i] / 16;
result *= 10;
result += src[i] % 16;
}
return result;
}
bool parse_online_card_tariff(uint16_t tariff_num, FuriString* tariff_name) {
bool tariff_parsed = false;
switch(tariff_num) {
case 0x0100:
furi_string_set_str(tariff_name, "Standart (online)");
tariff_parsed = true;
break;
case 0x0101:
case 0x0121:
furi_string_set_str(tariff_name, "Standart (airtag)");
tariff_parsed = true;
break;
case 0x0401:
furi_string_set_str(tariff_name, "Student (50%% discount)");
tariff_parsed = true;
break;
case 0x0402:
furi_string_set_str(tariff_name, "Student (travel)");
tariff_parsed = true;
break;
case 0x0002:
furi_string_set_str(tariff_name, "School (50%% discount)");
tariff_parsed = true;
break;
case 0x0505:
furi_string_set_str(tariff_name, "Social (large families)");
tariff_parsed = true;
break;
case 0x0528:
furi_string_set_str(tariff_name, "Social (handicapped)");
tariff_parsed = true;
break;
default:
furi_string_set_str(tariff_name, "Unknown");
tariff_parsed = false;
break;
}
return tariff_parsed;
}
static bool zolotaya_korona_online_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// Verify info sector data (card number prefix)
const uint8_t start_trip_block_number =
mf_classic_get_first_block_num_of_sector(TRIP_SECTOR_NUM);
const uint8_t start_info_block_number =
mf_classic_get_first_block_num_of_sector(INFO_SECTOR_NUM);
const uint8_t* block_start_ptr = &data->block[start_info_block_number].data[3];
// Validate card number
bool is_bcd;
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &is_bcd);
if(!is_bcd) break;
if(card_number_prefix != 9643) break;
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &is_bcd) / 10;
if(!is_bcd) break;
// Parse data
FuriString* tariff_name = furi_string_alloc();
block_start_ptr = &data->block[start_info_block_number].data[1];
const uint16_t tariff = nfc_util_bytes2num(block_start_ptr, 2);
parse_online_card_tariff(tariff, tariff_name);
block_start_ptr = &data->block[start_trip_block_number].data[0];
const uint8_t region_number = nfc_util_bytes2num(block_start_ptr, 1);
furi_string_cat_printf(
parsed_data,
"\e#Zolotaya korona\nCard number: %u%015llu\nTariff: %02X.%02X: %s\nRegion: %u\n",
card_number_prefix,
card_number_postfix,
tariff / 256,
tariff % 256,
furi_string_get_cstr(tariff_name),
region_number);
furi_string_free(tariff_name);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin zolotaya_korona_online_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = NULL,
.read = NULL,
.parse = zolotaya_korona_online_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &zolotaya_korona_online_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() {
return &zolotaya_korona_online_plugin_descriptor;
}

View File

@@ -23,6 +23,7 @@ ADD_SCENE(nfc, debug, Debug)
ADD_SCENE(nfc, field, Field)
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
ADD_SCENE(nfc, save_confirm, SaveConfirm)
ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite)
ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess)
@@ -36,6 +37,8 @@ ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass)
ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo)
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
ADD_SCENE(nfc, emv_more_info, EmvMoreInfo)
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader)
ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo)

View File

@@ -51,8 +51,11 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) {
if(nfc_delete(nfc)) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess);
} else {
scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneStart);
if(!scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneStart)) {
scene_manager_stop(nfc->scene_manager);
view_dispatcher_stop(nfc->view_dispatcher);
}
}
consumed = true;
}

View File

@@ -31,6 +31,11 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneFileSelect);
if(!consumed) {
scene_manager_stop(nfc->scene_manager);
view_dispatcher_stop(nfc->view_dispatcher);
}
}
}
}

View File

@@ -1,13 +1,47 @@
#include "../helpers/protocol_support/nfc_protocol_support.h"
#include "nfc_app_i.h"
#define NFC_EMULATION_TIME_MAX_MS (5 * 60 * 1000)
FuriTimer* timer_auto_exit;
void nfc_scene_emulate_timer_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventEmulationTimeExpired);
}
void nfc_scene_emulate_on_enter(void* context) {
NfcApp* instance = context;
nfc_protocol_support_on_enter(NfcProtocolSupportSceneEmulate, context);
timer_auto_exit =
furi_timer_alloc(nfc_scene_emulate_timer_callback, FuriTimerTypeOnce, instance);
furi_timer_start(timer_auto_exit, NFC_EMULATION_TIME_MAX_MS);
}
bool nfc_scene_emulate_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventEmulationTimeExpired) {
if(!scene_manager_previous_scene(instance->scene_manager)) {
scene_manager_stop(instance->scene_manager);
view_dispatcher_stop(instance->view_dispatcher);
} else {
scene_manager_previous_scene(instance->scene_manager);
}
return true;
}
}
return nfc_protocol_support_on_event(NfcProtocolSupportSceneEmulate, context, event);
}
void nfc_scene_emulate_on_exit(void* context) {
furi_timer_stop(timer_auto_exit);
furi_timer_free(timer_auto_exit);
nfc_protocol_support_on_exit(NfcProtocolSupportSceneEmulate, context);
}

View File

@@ -0,0 +1,74 @@
#include "../nfc_app_i.h"
#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h"
#include "../helpers/protocol_support/emv/emv_render.h"
enum {
EmvMoreInfoStateMenu,
EmvMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index
};
enum SubmenuIndex {
SubmenuIndexTransactions,
SubmenuIndexDynamic, // dynamic indices start here
};
void nfc_scene_emv_more_info_on_enter(void* context) {
NfcApp* nfc = context;
Submenu* submenu = nfc->submenu;
text_box_set_font(nfc->text_box, TextBoxFontHex);
submenu_add_item(
submenu,
"Transactions",
SubmenuIndexTransactions,
nfc_protocol_support_common_submenu_callback,
nfc);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
}
bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) {
NfcApp* nfc = context;
bool consumed = false;
const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMoreInfo);
const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv);
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexTransactions) {
FuriString* temp_str = furi_string_alloc();
nfc_render_emv_transactions(&data->emv_application, temp_str);
widget_add_text_scroll_element(
nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
scene_manager_set_scene_state(
nfc->scene_manager,
NfcSceneEmvMoreInfo,
EmvMoreInfoStateItem + SubmenuIndexTransactions);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
if(state >= EmvMoreInfoStateItem) {
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
scene_manager_set_scene_state(
nfc->scene_manager, NfcSceneEmvMoreInfo, EmvMoreInfoStateMenu);
} else {
// Return directly to the Info scene
scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo);
}
consumed = true;
}
return consumed;
}
void nfc_scene_emv_more_info_on_exit(void* context) {
NfcApp* nfc = context;
// Clear views
widget_reset(nfc->widget);
submenu_reset(nfc->submenu);
}

View File

@@ -24,7 +24,7 @@ void nfc_scene_extra_actions_on_enter(void* context) {
instance);
submenu_add_item(
submenu,
"Mifare Classic Keys",
"MIFARE Classic Keys",
SubmenuIndexMfClassicKeys,
nfc_scene_extra_actions_submenu_callback,
instance);

View File

@@ -134,6 +134,13 @@ bool nfc_scene_mf_classic_detect_reader_on_event(void* context, SceneManagerEven
instance->listener = NULL;
}
mfkey32_logger_free(instance->mfkey32_logger);
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSaveSuccess)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneStart);
} else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadSuccess)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneReadSuccess);
}
}
return consumed;

View File

@@ -173,7 +173,6 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) {
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance);
instance->nfc_dict_context.is_card_present = true;
}
static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) {

View File

@@ -18,15 +18,16 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) {
widget_add_string_multiline_element(
instance->widget,
64,
32,
AlignCenter,
13,
AlignCenter,
AlignTop,
FontSecondary,
"Now use Mfkey32\nto extract keys");
"Now use Mfkey32 to extract \nkeys: lab.flipper.net/nfc-tools");
widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25);
widget_add_button_element(
instance->widget,
GuiButtonTypeCenter,
"OK",
GuiButtonTypeRight,
"Finish",
nfc_scene_mf_classic_mfkey_complete_callback,
instance);
@@ -38,7 +39,7 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeCenter) {
if(event.event == GuiButtonTypeRight) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneStart);
}

View File

@@ -65,8 +65,9 @@ static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) {
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial);
if(state == NfcSceneMfClassicWriteInitialStateCardSearch) {
popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter);
popup_set_text(
instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter);
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
} else {
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);

View File

@@ -46,8 +46,9 @@ static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) {
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite);
if(state == NfcSceneMfUltralightWriteStateCardSearch) {
popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter);
popup_set_text(
instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter);
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
} else {
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);

View File

@@ -28,8 +28,11 @@ bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerE
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventViewExit) {
bool was_saved =
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneSavedMenu);
instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess);
}
}
return consumed;

View File

@@ -0,0 +1,44 @@
#include "../nfc_app_i.h"
void nfc_scene_save_confirm_dialog_callback(DialogExResult result, void* context) {
NfcApp* nfc = context;
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
}
void nfc_scene_save_confirm_on_enter(void* context) {
NfcApp* nfc = context;
DialogEx* dialog_ex = nfc->dialog_ex;
dialog_ex_set_left_button_text(dialog_ex, "Skip");
dialog_ex_set_right_button_text(dialog_ex, "Save");
dialog_ex_set_header(dialog_ex, "Save the Key?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop);
dialog_ex_set_context(dialog_ex, nfc);
dialog_ex_set_result_callback(dialog_ex, nfc_scene_save_confirm_dialog_callback);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
}
bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) {
NfcApp* nfc = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultRight) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
consumed = true;
} else if(event.event == DialogExResultLeft) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
}
}
return consumed;
}
void nfc_scene_save_confirm_on_exit(void* context) {
NfcApp* nfc = context;
// Clean view
dialog_ex_reset(nfc->dialog_ex);
}

View File

@@ -28,9 +28,16 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneMfClassicKeys);
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else {
consumed = scene_manager_search_and_switch_to_another_scene(
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneFileSelect);
if(!consumed) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneSavedMenu);
}
}
}
}

View File

@@ -29,6 +29,8 @@ void nfc_scene_select_protocol_on_enter(void* context) {
"%s %s",
prefix,
nfc_device_get_protocol_name(instance->protocols_detected[i]));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
submenu_add_item(
submenu,
furi_string_get_cstr(temp_str),

View File

@@ -32,10 +32,20 @@ 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++) {
const char* name = nfc_data_generator_get_name(i);
submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance);
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);
}
furi_string_free(str);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}

View File

@@ -50,7 +50,7 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) {
if(m->state == DetectReaderStateDone) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Completed!");
canvas_draw_icon(canvas, 20, 23, &I_check_big_20x17);
canvas_draw_icon(canvas, 24, 23, &I_check_big_20x17);
} else {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Collecting...");

View File

@@ -24,12 +24,14 @@ typedef enum {
SubmenuIndexSommer_FM_868,
SubmenuIndexStilmatic,
SubmenuIndexDTMNeo433,
SubmenuIndexDeaMio433,
SubmenuIndexGibidi433,
SubmenuIndexNiceMHouse_433_92,
SubmenuIndexJCM_433_92,
SubmenuIndexFAACRCXT_433_92,
SubmenuIndexFAACRCXT_868,
SubmenuIndexNormstahl_433_92,
SubmenuIndexGeniusBravo433,
SubmenuIndexGSN,
SubmenuIndexAprimatic,
SubmenuIndexHCS101_433_92,

View File

@@ -81,7 +81,6 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) {
uint32_t frequency = 0;
float rssi_temp = 0;
uint32_t frequency_temp = 0;
CC1101Status status;
FuriHalSpiBusHandle* spi_bus = instance->spi_bus;
const SubGhzDevice* radio_device = instance->radio_device;
@@ -143,9 +142,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) {
frequency = cc1101_set_frequency(spi_bus, current_frequency);
cc1101_calibrate(spi_bus);
do {
status = cc1101_get_status(spi_bus);
} while(status.STATE != CC1101StateIDLE);
furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000));
cc1101_switch_to_rx(spi_bus);
furi_hal_spi_release(spi_bus);
@@ -191,9 +189,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) {
frequency = cc1101_set_frequency(spi_bus, i);
cc1101_calibrate(spi_bus);
do {
status = cc1101_get_status(spi_bus);
} while(status.STATE != CC1101StateIDLE);
furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000));
cc1101_switch_to_rx(spi_bus);
furi_hal_spi_release(spi_bus);

View File

@@ -63,6 +63,10 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e
#ifdef FURI_DEBUG
subghz_last_settings_log(subghz->last_settings);
#endif
// Disable Hopping before opening the receiver scene!
if(subghz->last_settings->enable_hopping) {
subghz->last_settings->enable_hopping = false;
}
subghz_last_settings_save(subghz->last_settings);
}

View File

@@ -50,6 +50,12 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
if(state == SubGhzRxKeyStateExit) {
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
if(!furi_string_empty(subghz->file_path_tmp)) {
subghz_delete_file(subghz);
}
}
subghz_txrx_set_preset(
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
scene_manager_search_and_switch_to_previous_scene(

View File

@@ -144,6 +144,11 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) ||
(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) {
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit);
if(subghz_scene_read_raw_update_filename(subghz)) {
furi_string_set(subghz->file_path_tmp, subghz->file_path);
} else {
furi_string_reset(subghz->file_path_tmp);
}
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving);
} else {
//Restore default setting
@@ -178,7 +183,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
break;
case SubGhzCustomEventViewReadRAWErase:
if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) {
if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) ||
(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) {
if(subghz_scene_read_raw_update_filename(subghz)) {
furi_string_set(subghz->file_path_tmp, subghz->file_path);
subghz_delete_file(subghz);

Some files were not shown because too many files have changed in this diff Show More