mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 12:42:30 +04:00
Compare commits
174 Commits
nfcrefacto
...
unlshd-066
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05b183f5e5 | ||
|
|
0b356c2d6d | ||
|
|
ee9092c8e5 | ||
|
|
2e7bb26eef | ||
|
|
5d28939c28 | ||
|
|
15a29e1483 | ||
|
|
c22b3b57bd | ||
|
|
1daa2fa377 | ||
|
|
1b45b8a17d | ||
|
|
e94beff204 | ||
|
|
82baf1e923 | ||
|
|
c416041379 | ||
|
|
10444b943e | ||
|
|
2308a54ada | ||
|
|
0ed6738a5b | ||
|
|
dc25bfb831 | ||
|
|
d6fcb04aa8 | ||
|
|
9bf8f1015d | ||
|
|
c477d1321a | ||
|
|
ec99b70b38 | ||
|
|
06a58ebd53 | ||
|
|
eb6fe0a4db | ||
|
|
c6a14e1a67 | ||
|
|
04cead1fc5 | ||
|
|
93732865ac | ||
|
|
6a5d63803a | ||
|
|
159aef022b | ||
|
|
f7c63c675b | ||
|
|
99d657fcfb | ||
|
|
00ceb2cd5d | ||
|
|
b51a754fd9 | ||
|
|
c1e0d02afc | ||
|
|
890c9e87ce | ||
|
|
961dd297dd | ||
|
|
d675563271 | ||
|
|
e027d5c3e8 | ||
|
|
fdcfd5996b | ||
|
|
a849d49c92 | ||
|
|
ff129e524a | ||
|
|
05479103a6 | ||
|
|
ff41d262dc | ||
|
|
8628a2c6a2 | ||
|
|
30914678c9 | ||
|
|
c4bf1fe717 | ||
|
|
1946aaf5e1 | ||
|
|
a7b2427007 | ||
|
|
f9101d8084 | ||
|
|
1c3cbec661 | ||
|
|
cea423742b | ||
|
|
8c5f28d6a0 | ||
|
|
6c2e332638 | ||
|
|
5f18532a59 | ||
|
|
63f072a819 | ||
|
|
df1d6503c0 | ||
|
|
972054b377 | ||
|
|
e1c89834df | ||
|
|
c145cad653 | ||
|
|
4261063c99 | ||
|
|
f8546937c0 | ||
|
|
3daaea6ecf | ||
|
|
4a84fbc6e3 | ||
|
|
d8800e9fe3 | ||
|
|
7fd921227c | ||
|
|
b6ad07b47c | ||
|
|
1a21f0e3c9 | ||
|
|
c666368446 | ||
|
|
91d614dd25 | ||
|
|
baca59927b | ||
|
|
4eb40ce948 | ||
|
|
00b1018e9e | ||
|
|
9c92338ddf | ||
|
|
8188b63522 | ||
|
|
0e172b67eb | ||
|
|
156948ec58 | ||
|
|
bbdda5a3d7 | ||
|
|
51a2e638ed | ||
|
|
ffd9d3c218 | ||
|
|
9513ff5307 | ||
|
|
12e736b283 | ||
|
|
a64c9534e2 | ||
|
|
4b3e8aba29 | ||
|
|
98d5718ec9 | ||
|
|
457aa5331f | ||
|
|
a61b5d4b4c | ||
|
|
ba074068b0 | ||
|
|
615a147973 | ||
|
|
c00776ca22 | ||
|
|
d0b9a3a4ae | ||
|
|
dc246ddb09 | ||
|
|
591a2f2b02 | ||
|
|
7aaa847835 | ||
|
|
42101c6594 | ||
|
|
3e9ecd2f4f | ||
|
|
725811d7b3 | ||
|
|
a56a52b4bb | ||
|
|
da18305119 | ||
|
|
c71d04a660 | ||
|
|
f3f68fd5d3 | ||
|
|
d4ce47c941 | ||
|
|
7413a78013 | ||
|
|
83967d6e06 | ||
|
|
4d1c6a8d20 | ||
|
|
c95a0a0171 | ||
|
|
feba1dbc24 | ||
|
|
5b578cf69d | ||
|
|
dafea08581 | ||
|
|
5c50224571 | ||
|
|
b510df47f9 | ||
|
|
2aec3ec5fd | ||
|
|
4b8c017302 | ||
|
|
b579bca227 | ||
|
|
3a676f7afa | ||
|
|
ca479303a1 | ||
|
|
5e649d8c41 | ||
|
|
0bc626ba1d | ||
|
|
3821ee7709 | ||
|
|
cb17189b15 | ||
|
|
80e8167054 | ||
|
|
eede5ed29e | ||
|
|
360fef7777 | ||
|
|
b83da5d3cb | ||
|
|
ae5d28fbc5 | ||
|
|
f4cd7c0100 | ||
|
|
7b68fd30ec | ||
|
|
ac222f1b0c | ||
|
|
b0e8e68909 | ||
|
|
ef5f6e2f70 | ||
|
|
30f6da3fa3 | ||
|
|
3a47154cdb | ||
|
|
d953d35991 | ||
|
|
4b74d13e10 | ||
|
|
98bf353287 | ||
|
|
eb8c751b31 | ||
|
|
9abad8704f | ||
|
|
6716c0f792 | ||
|
|
35f7ec6c07 | ||
|
|
a524fd7674 | ||
|
|
ff27fd3094 | ||
|
|
977ac09fe6 | ||
|
|
1a88e01899 | ||
|
|
6e710c5164 | ||
|
|
fd56ac3400 | ||
|
|
1d801c38f9 | ||
|
|
8afdb5b7b4 | ||
|
|
83624b1dee | ||
|
|
56adcf1ad8 | ||
|
|
ad27f87a0c | ||
|
|
346cf299ee | ||
|
|
05489fda7d | ||
|
|
1b12526357 | ||
|
|
802035d92e | ||
|
|
a24d0f1958 | ||
|
|
da68f2e4ed | ||
|
|
8f16dbb8e7 | ||
|
|
49e458f1b5 | ||
|
|
5cf46d2aa9 | ||
|
|
beedf54e75 | ||
|
|
70ccb89c3d | ||
|
|
5ea43a2a4b | ||
|
|
41f60dbbf4 | ||
|
|
827341ec08 | ||
|
|
5c36043d03 | ||
|
|
cf5811f8d9 | ||
|
|
ec6a169bf8 | ||
|
|
f1dec87c1b | ||
|
|
ab29951a99 | ||
|
|
bbe9f88bbe | ||
|
|
9188bf0013 | ||
|
|
f33ed59567 | ||
|
|
3fd8c80861 | ||
|
|
7b8ac3a5a0 | ||
|
|
6fef957001 | ||
|
|
0de1c9df89 | ||
|
|
a0e8cfbe97 |
@@ -175,3 +175,10 @@ Max butthurt: 12
|
|||||||
Min level: 2
|
Min level: 2
|
||||||
Max level: 3
|
Max level: 3
|
||||||
Weight: 4
|
Weight: 4
|
||||||
|
|
||||||
|
Name: L2_Secret_door_128x64
|
||||||
|
Min butthurt: 0
|
||||||
|
Max butthurt: 12
|
||||||
|
Min level: 2
|
||||||
|
Max level: 3
|
||||||
|
Weight: 4
|
||||||
|
|||||||
@@ -462,10 +462,10 @@ index 0000000..68dacda
|
|||||||
+ */
|
+ */
|
||||||
+const char* rgb_backlight_get_color_text(uint8_t index);
|
+const char* rgb_backlight_get_color_text(uint8_t index);
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/firmware/targets/f7/furi_hal/furi_hal_light.c
|
diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c
|
||||||
index 83e1603..45798ca 100644
|
index 83e1603..45798ca 100644
|
||||||
--- a/firmware/targets/f7/furi_hal/furi_hal_light.c
|
--- a/targets/f7/furi_hal/furi_hal_light.c
|
||||||
+++ b/firmware/targets/f7/furi_hal/furi_hal_light.c
|
+++ b/targets/f7/furi_hal/furi_hal_light.c
|
||||||
@@ -3,6 +3,7 @@
|
@@ -3,6 +3,7 @@
|
||||||
#include <furi_hal_light.h>
|
#include <furi_hal_light.h>
|
||||||
#include <lp5562.h>
|
#include <lp5562.h>
|
||||||
|
|||||||
28
.drone.yml
28
.drone.yml
@@ -45,7 +45,9 @@ steps:
|
|||||||
- export FORCE_NO_DIRTY=yes
|
- export FORCE_NO_DIRTY=yes
|
||||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||||
- tar zxvf all-the-apps-base.tgz
|
- tar zxf all-the-apps-base.tgz
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps_data
|
||||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||||
- rm -rf base_pack_build
|
- rm -rf base_pack_build
|
||||||
@@ -65,7 +67,8 @@ steps:
|
|||||||
pull: never
|
pull: never
|
||||||
commands:
|
commands:
|
||||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
||||||
- tar zxvf all-the-apps-extra.tgz
|
- tar zxf all-the-apps-extra.tgz
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps
|
||||||
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
||||||
- rm -rf extra_pack_build
|
- rm -rf extra_pack_build
|
||||||
- export DIST_SUFFIX=${DRONE_TAG}e
|
- export DIST_SUFFIX=${DRONE_TAG}e
|
||||||
@@ -117,7 +120,9 @@ steps:
|
|||||||
- rm -f build/f7-firmware-C/toolbox/version.*
|
- rm -f build/f7-firmware-C/toolbox/version.*
|
||||||
- ./fbt COMPACT=1 DEBUG=0 updater_package
|
- ./fbt COMPACT=1 DEBUG=0 updater_package
|
||||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||||
- tar zxvf all-the-apps-base.tgz
|
- tar zxf all-the-apps-base.tgz
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps_data
|
||||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||||
- rm -rf base_pack_build
|
- rm -rf base_pack_build
|
||||||
@@ -336,9 +341,10 @@ steps:
|
|||||||
DISCORD_WEBHOOK:
|
DISCORD_WEBHOOK:
|
||||||
from_secret: dis_release_webhook
|
from_secret: dis_release_webhook
|
||||||
commands:
|
commands:
|
||||||
- wget "https://raw.githubusercontent.com/fieu/discord.sh/e1dc1a7595efad2cad8f072f0b3531c470f5b7c8/discord.sh"
|
- wget "https://raw.githubusercontent.com/fieu/discord.sh/2253303efc0e7211ac2777d2535054cbb872f1e0/discord.sh"
|
||||||
- chmod +x ./discord.sh
|
- chmod +x ./discord.sh
|
||||||
- ./discord.sh --text 'New Unleashed firmware released!\n\nVersion - '${DRONE_TAG}'\n\n[-> Sponsor our project](https://boosty.to/mmxdev)\n\n[[Github - Changelog]](https://github.com/DarkFlippers/unleashed-firmware/releases/tag/'${DRONE_TAG}')\n\n[-How to install firmware-](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)\n\n[-Download latest extra apps pack-](https://github.com/xMasterX/all-the-plugins/releases/latest)\n\n[-Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/'${DRONE_TAG}'/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=release-cfw&version='${DRONE_TAG}')\n\n[-Version with only main apps - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&channel=release-cfw&version='${DRONE_TAG}'c)\n\n[-Version without custom animations - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_no_anim/flipper-z-f7-update-'${DRONE_TAG}'n.tgz&channel=release-cfw&version='${DRONE_TAG}'n)\n\n[-Version with RGB patch - only for hardware mod! - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'r.tgz&channel=release-cfw&version='${DRONE_TAG}'r)\n\n[-Version with RGB patch - only for hardware mod! - Direct download-](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'r.tgz)\n\n[-Version with Extra apps - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&channel=release-cfw&version='${DRONE_TAG}'e)'
|
- echo 'New Unleashed firmware released!\n\nVersion - '${DRONE_TAG}'\n\n[-> Sponsor our project](https://boosty.to/mmxdev)\n\n[[Github - Changelog]](https://github.com/DarkFlippers/unleashed-firmware/releases/tag/'${DRONE_TAG}')\n\n[-How to install firmware-](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)\n\n[-Download latest extra apps pack-](https://github.com/xMasterX/all-the-plugins/releases/latest)\n\n[-Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/'${DRONE_TAG}'/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=release-cfw&version='${DRONE_TAG}')\n\n[-Version with only main apps - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'c.tgz&channel=release-cfw&version='${DRONE_TAG}'c)\n\n[-Version without custom animations - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_no_anim/flipper-z-f7-update-'${DRONE_TAG}'n.tgz&channel=release-cfw&version='${DRONE_TAG}'n)\n\n[-Version with RGB patch - only for hardware mod! - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'r.tgz&channel=release-cfw&version='${DRONE_TAG}'r)\n\n[-Version with RGB patch - only for hardware mod! - Direct download-](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'r.tgz)\n\n[-Version with Extra apps - Install FW via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_TAG}'e.tgz&channel=release-cfw&version='${DRONE_TAG}'e)' > messagedisc.txt
|
||||||
|
- ./discord.sh --text "$(jq -Rs . <messagedisc.txt | cut -c 2- | rev | cut -c 2- | rev)"
|
||||||
|
|
||||||
- name: "Send clean build to telegram"
|
- name: "Send clean build to telegram"
|
||||||
image: appleboy/drone-telegram
|
image: appleboy/drone-telegram
|
||||||
@@ -420,7 +426,9 @@ steps:
|
|||||||
- export FORCE_NO_DIRTY=yes
|
- export FORCE_NO_DIRTY=yes
|
||||||
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
- export FBT_GIT_SUBMODULE_SHALLOW=1
|
||||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-base.tgz
|
||||||
- tar zxvf all-the-apps-base.tgz
|
- tar zxf all-the-apps-base.tgz
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps_data
|
||||||
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
- cp -R base_pack_build/artifacts-base/* applications/main/clock_app/resources/apps/
|
||||||
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
- cp -R base_pack_build/apps_data/* applications/main/clock_app/resources/apps_data/
|
||||||
- rm -rf base_pack_build
|
- rm -rf base_pack_build
|
||||||
@@ -440,7 +448,8 @@ steps:
|
|||||||
pull: never
|
pull: never
|
||||||
commands:
|
commands:
|
||||||
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
- wget https://github.com/xMasterX/all-the-plugins/releases/latest/download/all-the-apps-extra.tgz
|
||||||
- tar zxvf all-the-apps-extra.tgz
|
- tar zxf all-the-apps-extra.tgz
|
||||||
|
- mkdir -p applications/main/clock_app/resources/apps
|
||||||
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
- cp -R extra_pack_build/artifacts-extra/* applications/main/clock_app/resources/apps/
|
||||||
- rm -rf extra_pack_build
|
- rm -rf extra_pack_build
|
||||||
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}e
|
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}e
|
||||||
@@ -657,9 +666,10 @@ steps:
|
|||||||
DISCORD_WEBHOOK:
|
DISCORD_WEBHOOK:
|
||||||
from_secret: dis_dev_webhook
|
from_secret: dis_dev_webhook
|
||||||
commands:
|
commands:
|
||||||
- wget "https://raw.githubusercontent.com/fieu/discord.sh/e1dc1a7595efad2cad8f072f0b3531c470f5b7c8/discord.sh"
|
- wget "https://raw.githubusercontent.com/fieu/discord.sh/2253303efc0e7211ac2777d2535054cbb872f1e0/discord.sh"
|
||||||
- chmod +x ./discord.sh
|
- chmod +x ./discord.sh
|
||||||
- ./discord.sh --text 'Unleashed firmware dev build successful!\n\nBuild - '${DRONE_BUILD_NUMBER}'\n\nCommit - https://github.com/DarkFlippers/unleashed-firmware/commit/'${DRONE_COMMIT_SHA}'\n\n[-> Sponsor our project](https://boosty.to/mmxdev)\n\n[-Version with Extra apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'e.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'e)\n\n[-Version with only main apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'c.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'c)\n\n[-Version with RGB patch - only for hardware mod! - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'r.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'r)\n\n[-Version with RGB patch - only for hardware mod! - Direct download-](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'r.tgz)\n\n[-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}')'
|
- echo 'Unleashed firmware dev build successful!\n\nBuild - '${DRONE_BUILD_NUMBER}'\n\nCommit - https://github.com/DarkFlippers/unleashed-firmware/commit/'${DRONE_COMMIT_SHA}'\n\n[-> Sponsor our project](https://boosty.to/mmxdev)\n\n[-Version with Extra apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'e.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'e)\n\n[-Version with only main apps - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'c.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'c)\n\n[-Version with RGB patch - only for hardware mod! - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'r.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}'r)\n\n[-Version with RGB patch - only for hardware mod! - Direct download-](https://unleashedflip.com/fw_extra_apps/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'r.tgz)\n\n[-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/fw/dev/flipper-z-f7-update-'${DRONE_BUILD_NUMBER}'.tgz&channel=dev-cfw&version='${DRONE_BUILD_NUMBER}')' > messagedisc.txt
|
||||||
|
- ./discord.sh --text "$(jq -Rs . <messagedisc.txt | cut -c 2- | rev | cut -c 2- | rev)"
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||||
|
|||||||
88
CHANGELOG.md
88
CHANGELOG.md
@@ -1,13 +1,79 @@
|
|||||||
|
## Warning!!! Please read this before installing!!!
|
||||||
|
**This release has some unresolved issues, if any of those affects your daily usage, stay at 065 release or wait for next releases:** <br>
|
||||||
|
**Issues from this list will be fixed in next releases**
|
||||||
|
### Known NFC app regressions and issues:
|
||||||
|
- Mifare Classic with custom UID add manually option was temporarily removed (Unleashed)
|
||||||
|
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
|
||||||
|
- Mifare Classic dict attack fast skip (multiple presses on OK button) causes glitches/incorrect reading (OFW)
|
||||||
|
- EMV simple data parser was removed with protocol with refactoring (OFW)
|
||||||
|
- Mifare Classic Emulation slow response (unconfirmed) (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)
|
||||||
|
### Some apps that was made for old nfc stack is now not compatible with the new API and require complete remake:
|
||||||
|
**If you want to help with making this apps work again please send PR to the repo at link below**
|
||||||
|
- Current list of affected apps: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors
|
||||||
|
- Also in app **Enhanced Sub-GHz Chat** - NFC part was temporarily removed to make app usable, NFC part of the app requires remaking it with new nfc stack <br>
|
||||||
|
**API was updated to v49.x**
|
||||||
## New changes
|
## New changes
|
||||||
* SubGHz: Add 4 more systems to Add Manually (untested!)
|
* NFC: Added new parsers for transport cards - Umarsh, Kazan, Moscow, Metromoney(Tbilisi), and fixes for OFW parsers (by @assasinfil and @Leptopt1los) (special thanks for users who provided various dumps of those cards for research)
|
||||||
* SubGHz: Add Manually fixes
|
* NFC: Added simple key name display to UI to fix regression
|
||||||
* SubGHz: Added NiceFlor-S to ignore options, removed colons. (by @G2Dolphin | PR #620)
|
* NFC: Add keys to mf_classic_dict (by @hnlcory | PR #660)
|
||||||
|
* NFC: Add Saflok and MyKey KDFs (by @noproto | PR #662)
|
||||||
|
* NFC: social_moscow parser verification collisions fix (by @Leptopt1los)
|
||||||
|
* iButton: Fix UI text - protocol name getting out of screen bounds when key name is too large, and other related issues (by @krolchonok | PR #649)
|
||||||
|
* SubGHz: Fixed feature naming in menu
|
||||||
|
* SubGHz: Added honeywell protocol [(by @htotoo)](https://github.com/Flipper-XFW/Xtreme-Firmware/commit/ceee551befa0cb8fd8514a4f8a1250fd9e0997ee)
|
||||||
|
* SubGHz: Add 303.9 Mhz to default frequency list
|
||||||
|
* SubGHz: Fix Keeloq decoding order bug (random switch to HCS101 or anmotors)
|
||||||
|
* SubGHz: Fix secplus v1 key display issue
|
||||||
|
* API: Add new get function for varitemlist (by @Willy-JL)
|
||||||
* Misc code cleanup
|
* Misc code cleanup
|
||||||
* RGB: Fix white color on reboot, move settings, add custom color option
|
* Apps: **Bluetooth Remote / USB Keyboard & Mouse** - `Movie` and `PTT` modes by @hryamzik
|
||||||
* **BLE Spam app** updated to latest version (Android, Windows support) (by @Willy-JL) -> (app can be found in builds ` `, `e`, `n`, `r`)
|
* Apps: **BLE Spam app** updated to latest version (New devices support, + Menu by holding Start) (by @Willy-JL) -> (app can be found in builds ` `, `e`, `n`, `r`)
|
||||||
* OFW: Fix double arrows and add proper indication
|
* Apps: **NFC Magic** - Gen4 Actions (option to fix card with broken config) (by @Leptopt1los and @xMasterX)
|
||||||
* OFW: SubGHz: add manually fix 12-bits is 0xFFF (or 0xFF0) CAME/NICE 12-bit
|
* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
|
||||||
* OFW: Fix various crashes if debug libraries used
|
* OFW: NFC fixes
|
||||||
|
* OFW: nfc: m1k-based Aime (non-AIC) card support
|
||||||
|
* OFW: SubGhz: fix count bit for detect gate_tx protocol
|
||||||
|
* OFW: Fixed a zero allocation error when reading an iso15693 nfc tag with no additional blocks.
|
||||||
|
* OFW: Ntag21x write
|
||||||
|
* OFW: Mifare Classic nested auth support
|
||||||
|
* OFW: ST25TB poller refining + write support
|
||||||
|
* OFW: Libraries cleanup; u2f crypto rework to use mbedtls
|
||||||
|
* OFW: Add the secret door animation
|
||||||
|
* OFW: Allows you to use UCS-2 in canvas_glyph_width
|
||||||
|
* OFW: Mifare Classic fixes
|
||||||
|
* OFW: NFC: Felica UID emulation
|
||||||
|
* OFW: 64k does not enough
|
||||||
|
* OFW: fbt: improvements
|
||||||
|
* OFW: Various Fixes for 0.95
|
||||||
|
* OFW: Add Mastercode SubGHz Protocol
|
||||||
|
* OFW: Do not remove file when renaming to itself
|
||||||
|
* OFW: Fix iButton crash on missing file
|
||||||
|
* OFW: NFC API improvements
|
||||||
|
* OFW: MF Ultralight no pwd polling adjustment
|
||||||
|
* OFW: Fix limited_credit_value having wrong value in mf_desfire_file_settings_parse
|
||||||
|
* OFW: Infrared remote button index support
|
||||||
|
* OFW: Fix NFC unit tests
|
||||||
|
* OFW: fix: invariant format of log time data
|
||||||
|
* OFW: fbt: dist improvements
|
||||||
|
* OFW: Fix crash when exiting write mode
|
||||||
|
* OFW: Dolphin: Extreme butthurt loop fix
|
||||||
|
* OFW: **Furi, FuriHal: remove FreeRTOS headers leaks**
|
||||||
|
* OFW: fbt: source collection improvements
|
||||||
|
* OFW: Rename menu items related to dummy mode and sound
|
||||||
|
* OFW: fbt: SD card resource handling speedup
|
||||||
|
* OFW: **Furi: cleanup crash use**
|
||||||
|
* OFW: Allow for larger Infrared remotes
|
||||||
|
* OFW: **fbt: reworked assets & resources handling**
|
||||||
|
* OFW: Storage: speedup write_chunk cli command
|
||||||
|
* OFW: fix crash after st25tb save
|
||||||
|
* OFW: Fix crash when reading files > 64B
|
||||||
|
* OFW: NFC RC fixes
|
||||||
|
* OFW: Fix MF DESFire record file handling
|
||||||
|
* OFW: **NFC refactoring** (new NFC stack) -> some apps still require very big changes to make them work with new system - see apps that was temporarily removed from this release here: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors
|
||||||
|
* OFW: fbt: glob & git improvements
|
||||||
|
* OFW: FastFAP: human readable error log
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -31,8 +97,8 @@
|
|||||||
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||||
|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`|
|
|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`|
|
||||||
|
|
||||||
#### Thanks to our sponsors:
|
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
|
||||||
callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
|
ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
|
||||||
and all other great people who supported our project and me (xMasterX), thanks to you all!
|
and all other great people who supported our project and me (xMasterX), thanks to you all!
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +116,7 @@ What build I should download and what this name means - `flipper-z-f7-update-(ve
|
|||||||
| `c` | ✅ | | | |
|
| `c` | ✅ | | | |
|
||||||
| `n` | | ✅ | | |
|
| `n` | | ✅ | | |
|
||||||
| `e` | ✅ | ✅ | ✅ | |
|
| `e` | ✅ | ✅ | ✅ | |
|
||||||
| `r` | ✅ | ✅ | ✅ | ✅ |
|
| `r` | ✅ | ✅ | ✅ | ⚠️ |
|
||||||
|
|
||||||
⚠️This is [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not install on non modded device!
|
⚠️This is [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not install on non modded device!
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
- New frequency analyzer [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43)
|
- New frequency analyzer [(by ClusterM)](https://github.com/DarkFlippers/unleashed-firmware/pull/43)
|
||||||
- Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
- Press OK in frequency analyzer to use detected frequency in Read modes [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/77)
|
||||||
- Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79)
|
- Long press OK button in Sub-GHz Frequency analyzer to switch to Read menu [(by derskythe)](https://github.com/DarkFlippers/unleashed-firmware/pull/79)
|
||||||
- New option to use timestamps + protocol name when you saving file, instead of random name - Enable in `Radio Settings -> Time in names = ON`
|
- New option to use timestamps + protocol name when you saving file, instead of random name or timestamp only - Enable in `Radio Settings -> Protocol Names = ON`
|
||||||
- Read mode UI improvements (shows time when signal was received) (by @wosk)
|
- Read mode UI improvements (shows time when signal was received) (by @wosk)
|
||||||
- External CC1101 module support (Hardware SPI used)
|
- External CC1101 module support (Hardware SPI used)
|
||||||
- **Hold right in received signal list to delete selected signal**
|
- **Hold right in received signal list to delete selected signal**
|
||||||
@@ -140,9 +140,9 @@ 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.
|
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.
|
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.
|
||||||
- @gid9798 - SubGHz, Plugins, many other things
|
- @gid9798 - SubGHz, Plugins, many other things
|
||||||
- @assasinfil - SubGHz protocols
|
- @assasinfil - SubGHz protocols, NFC parsers (working with @Leptopt1los)
|
||||||
- @Svaarich - UI design and animations
|
- @Svaarich - UI design and animations
|
||||||
- @amec0e & @Leptopt1los - Infrared assets
|
- @amec0e & @Leptopt1los (only ACs) - Infrared assets
|
||||||
- Community moderators in Telegram, Discord, and Reddit
|
- 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.
|
- And of course our GitHub community. Your PRs are a very important part of this firmware and open-source development.
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM
|
|||||||
### Official Flipper Zero Apps Catalog [web version](https://lab.flipper.net/apps) or mobile app
|
### Official Flipper Zero Apps Catalog [web version](https://lab.flipper.net/apps) or mobile app
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
## First lock official docs [docs.flipper.net](https://docs.flipper.net/)
|
## First look at official docs [docs.flipper.net](https://docs.flipper.net/)
|
||||||
## [How to install](/documentation/HowToInstall.md) - [versions info](/CHANGELOG.md#recommended-update-option---web-updater): `n`,` `,`e`...
|
## [How to install](/documentation/HowToInstall.md) - [versions info](/CHANGELOG.md#recommended-update-option---web-updater): `n`,` `,`e`...
|
||||||
## Firmware & Development
|
## Firmware & Development
|
||||||
|
|
||||||
|
|||||||
47
SConstruct
47
SConstruct
@@ -172,17 +172,19 @@ Alias("fap_dist", fap_dist)
|
|||||||
|
|
||||||
fap_deploy = distenv.PhonyTarget(
|
fap_deploy = distenv.PhonyTarget(
|
||||||
"fap_deploy",
|
"fap_deploy",
|
||||||
[
|
Action(
|
||||||
[
|
[
|
||||||
"${PYTHON3}",
|
[
|
||||||
"${FBT_SCRIPT_DIR}/storage.py",
|
"${PYTHON3}",
|
||||||
"-p",
|
"${FBT_SCRIPT_DIR}/storage.py",
|
||||||
"${FLIP_PORT}",
|
"-p",
|
||||||
"send",
|
"${FLIP_PORT}",
|
||||||
"${SOURCE}",
|
"send",
|
||||||
"/ext/apps",
|
"${SOURCE}",
|
||||||
|
"/ext/apps",
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
),
|
||||||
source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")),
|
source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")),
|
||||||
)
|
)
|
||||||
Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"])
|
Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"])
|
||||||
@@ -261,7 +263,7 @@ distenv.PhonyTarget(
|
|||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"debug_other_blackmagic",
|
"debug_other_blackmagic",
|
||||||
"${GDBPYCOM}",
|
"${GDBPYCOM}",
|
||||||
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||||
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
||||||
GDBPYOPTS=debug_other_opts,
|
GDBPYOPTS=debug_other_opts,
|
||||||
)
|
)
|
||||||
@@ -276,23 +278,27 @@ distenv.PhonyTarget(
|
|||||||
# Linter
|
# Linter
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"lint",
|
"lint",
|
||||||
"${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}",
|
[["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]],
|
||||||
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
||||||
)
|
)
|
||||||
|
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"format",
|
"format",
|
||||||
"${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}",
|
[["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]],
|
||||||
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
||||||
)
|
)
|
||||||
|
|
||||||
# PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests
|
# PY_LINT_SOURCES contains recursively-built modules' SConscript files
|
||||||
# Here we add additional Python files residing in repo root
|
# Here we add additional Python files residing in repo root
|
||||||
firmware_env.Append(
|
firmware_env.Append(
|
||||||
PY_LINT_SOURCES=[
|
PY_LINT_SOURCES=[
|
||||||
# Py code folders
|
# Py code folders
|
||||||
"site_scons",
|
"site_scons",
|
||||||
"scripts",
|
"scripts",
|
||||||
|
"applications",
|
||||||
|
"applications_user",
|
||||||
|
"assets",
|
||||||
|
"targets",
|
||||||
# Extra files
|
# Extra files
|
||||||
"SConstruct",
|
"SConstruct",
|
||||||
"firmware.scons",
|
"firmware.scons",
|
||||||
@@ -302,7 +308,10 @@ firmware_env.Append(
|
|||||||
|
|
||||||
|
|
||||||
black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}"
|
black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}"
|
||||||
black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"']
|
black_base_args = [
|
||||||
|
"--include",
|
||||||
|
'"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"',
|
||||||
|
]
|
||||||
|
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"lint_py",
|
"lint_py",
|
||||||
@@ -323,10 +332,14 @@ distenv.PhonyTarget(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Start Flipper CLI via PySerial's miniterm
|
# Start Flipper CLI via PySerial's miniterm
|
||||||
distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py -p ${FLIP_PORT}")
|
distenv.PhonyTarget(
|
||||||
|
"cli", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]]
|
||||||
|
)
|
||||||
|
|
||||||
# Update WiFi devboard firmware
|
# Update WiFi devboard firmware
|
||||||
distenv.PhonyTarget("devboard_flash", "${PYTHON3} ${FBT_SCRIPT_DIR}/wifi_board.py")
|
distenv.PhonyTarget(
|
||||||
|
"devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Find blackmagic probe
|
# Find blackmagic probe
|
||||||
@@ -361,5 +374,5 @@ distenv.Alias("vscode_dist", vscode_dist)
|
|||||||
# Configure shell with build tools
|
# Configure shell with build tools
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"env",
|
"env",
|
||||||
"@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)",
|
"@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ App(
|
|||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
targets=["f7"],
|
targets=["f7"],
|
||||||
entry_point="accessor_app",
|
entry_point="accessor_app",
|
||||||
cdefines=["APP_ACCESSOR"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=4 * 1024,
|
stack_size=4 * 1024,
|
||||||
order=40,
|
order=40,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Battery Test",
|
name="Battery Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="battery_test_app",
|
entry_point="battery_test_app",
|
||||||
cdefines=["APP_BATTERY_TEST"],
|
|
||||||
requires=[
|
requires=[
|
||||||
"gui",
|
"gui",
|
||||||
"power",
|
"power",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Blink Test",
|
name="Blink Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="blink_test_app",
|
entry_point="blink_test_app",
|
||||||
cdefines=["APP_BLINK"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=10,
|
order=10,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="CCID Debug",
|
name="CCID Debug",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="ccid_test_app",
|
entry_point="ccid_test_app",
|
||||||
cdefines=["CCID_TEST"],
|
|
||||||
requires=[
|
requires=[
|
||||||
"gui",
|
"gui",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ struct ISO7816_Command_APDU {
|
|||||||
//body
|
//body
|
||||||
uint8_t Lc;
|
uint8_t Lc;
|
||||||
uint8_t Le;
|
uint8_t Le;
|
||||||
} __attribute__((packed));
|
} FURI_PACKED;
|
||||||
|
|
||||||
struct ISO7816_Response_APDU {
|
struct ISO7816_Response_APDU {
|
||||||
uint8_t SW1;
|
uint8_t SW1;
|
||||||
uint8_t SW2;
|
uint8_t SW2;
|
||||||
} __attribute__((packed));
|
} FURI_PACKED;
|
||||||
|
|
||||||
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
|
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
|
||||||
void iso7816_read_command_apdu(
|
void iso7816_read_command_apdu(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Crash Test",
|
name="Crash Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="crash_test_app",
|
entry_point="crash_test_app",
|
||||||
cdefines=["APP_CRASH_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
fap_category="Debug",
|
fap_category="Debug",
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ App(
|
|||||||
name="Display Test",
|
name="Display Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="display_test_app",
|
entry_point="display_test_app",
|
||||||
cdefines=["APP_DISPLAY_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
fap_libs=["misc"],
|
fap_libs=["u8g2"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=120,
|
order=120,
|
||||||
fap_category="Debug",
|
fap_category="Debug",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="File Browser Test",
|
name="File Browser Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="file_browser_app",
|
entry_point="file_browser_app",
|
||||||
cdefines=["APP_FILE_BROWSER_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=2 * 1024,
|
stack_size=2 * 1024,
|
||||||
order=150,
|
order=150,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Keypad Test",
|
name="Keypad Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="keypad_test_app",
|
entry_point="keypad_test_app",
|
||||||
cdefines=["APP_KEYPAD_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=30,
|
order=30,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Locale Test",
|
name="Locale Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="locale_test_app",
|
entry_point="locale_test_app",
|
||||||
cdefines=["APP_LOCALE"],
|
|
||||||
requires=["gui", "locale"],
|
requires=["gui", "locale"],
|
||||||
stack_size=2 * 1024,
|
stack_size=2 * 1024,
|
||||||
order=70,
|
order=70,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Text Box Test",
|
name="Text Box Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="text_box_test_app",
|
entry_point="text_box_test_app",
|
||||||
cdefines=["APP_TEXT_BOX_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=140,
|
order=140,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="UART Echo",
|
name="UART Echo",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="uart_echo_app",
|
entry_point="uart_echo_app",
|
||||||
cdefines=["APP_UART_ECHO"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=2 * 1024,
|
stack_size=2 * 1024,
|
||||||
order=70,
|
order=70,
|
||||||
|
|||||||
@@ -3,18 +3,29 @@
|
|||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include "../minunit.h"
|
#include "../minunit.h"
|
||||||
|
|
||||||
void test_furi_create_open() {
|
#define TEST_RECORD_NAME "test/holding"
|
||||||
// 1. Create record
|
|
||||||
uint8_t test_data = 0;
|
|
||||||
furi_record_create("test/holding", (void*)&test_data);
|
|
||||||
|
|
||||||
// 2. Open it
|
void test_furi_create_open() {
|
||||||
void* record = furi_record_open("test/holding");
|
// Test that record does not exist
|
||||||
|
mu_check(furi_record_exists(TEST_RECORD_NAME) == false);
|
||||||
|
|
||||||
|
// Create record
|
||||||
|
uint8_t test_data = 0;
|
||||||
|
furi_record_create(TEST_RECORD_NAME, (void*)&test_data);
|
||||||
|
|
||||||
|
// Test that record exists
|
||||||
|
mu_check(furi_record_exists(TEST_RECORD_NAME) == true);
|
||||||
|
|
||||||
|
// Open it
|
||||||
|
void* record = furi_record_open(TEST_RECORD_NAME);
|
||||||
mu_assert_pointers_eq(record, &test_data);
|
mu_assert_pointers_eq(record, &test_data);
|
||||||
|
|
||||||
// 3. Close it
|
// Close it
|
||||||
furi_record_close("test/holding");
|
furi_record_close(TEST_RECORD_NAME);
|
||||||
|
|
||||||
// 4. Clean up
|
// Clean up
|
||||||
furi_record_destroy("test/holding");
|
furi_record_destroy(TEST_RECORD_NAME);
|
||||||
|
|
||||||
|
// Test that record does not exist
|
||||||
|
mu_check(furi_record_exists(TEST_RECORD_NAME) == false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
|
|||||||
|
|
||||||
void minunit_print_progress(void);
|
void minunit_print_progress(void);
|
||||||
void minunit_print_fail(const char* error);
|
void minunit_print_fail(const char* error);
|
||||||
|
void minunit_printf_warning(const char* format, ...);
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
#define MU_TEST(method_name) static void method_name(void)
|
#define MU_TEST(method_name) static void method_name(void)
|
||||||
@@ -150,6 +151,10 @@ void minunit_print_fail(const char* error);
|
|||||||
minunit_end_proc_timer - minunit_proc_timer);)
|
minunit_end_proc_timer - minunit_proc_timer);)
|
||||||
#define MU_EXIT_CODE minunit_fail
|
#define MU_EXIT_CODE minunit_fail
|
||||||
|
|
||||||
|
/* Warnings */
|
||||||
|
#define mu_warn(message) \
|
||||||
|
MU__SAFE_BLOCK(minunit_printf_warning("%s:%d: %s", __FILE__, __LINE__, message);)
|
||||||
|
|
||||||
/* Assertions */
|
/* Assertions */
|
||||||
#define mu_check(test) \
|
#define mu_check(test) \
|
||||||
MU__SAFE_BLOCK( \
|
MU__SAFE_BLOCK( \
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
#include <nfc/nfc_poller.h>
|
#include <nfc/nfc_poller.h>
|
||||||
#include <nfc/nfc_listener.h>
|
#include <nfc/nfc_listener.h>
|
||||||
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
|
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h>
|
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h>
|
||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
|
||||||
#include <nfc/helpers/nfc_dict.h>
|
#include <nfc/helpers/nfc_dict.h>
|
||||||
#include <nfc/nfc.h>
|
#include <nfc/nfc.h>
|
||||||
@@ -182,8 +182,8 @@ MU_TEST(iso14443_3a_reader) {
|
|||||||
|
|
||||||
Iso14443_3aData iso14443_3a_poller_data = {};
|
Iso14443_3aData iso14443_3a_poller_data = {};
|
||||||
mu_assert(
|
mu_assert(
|
||||||
iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
|
iso14443_3a_poller_sync_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
|
||||||
"iso14443_3a_poller_read() failed");
|
"iso14443_3a_poller_sync_read() failed");
|
||||||
|
|
||||||
nfc_listener_stop(iso3_listener);
|
nfc_listener_stop(iso3_listener);
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@@ -203,15 +203,26 @@ static void mf_ultralight_reader_test(const char* path) {
|
|||||||
NfcDevice* nfc_device = nfc_device_alloc();
|
NfcDevice* nfc_device = nfc_device_alloc();
|
||||||
mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n");
|
mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n");
|
||||||
|
|
||||||
NfcListener* mfu_listener = nfc_listener_alloc(
|
MfUltralightData* data =
|
||||||
listener,
|
(MfUltralightData*)nfc_device_get_data(nfc_device, NfcProtocolMfUltralight);
|
||||||
NfcProtocolMfUltralight,
|
|
||||||
nfc_device_get_data(nfc_device, NfcProtocolMfUltralight));
|
uint32_t features = mf_ultralight_get_feature_support_set(data->type);
|
||||||
|
bool pwd_supported =
|
||||||
|
mf_ultralight_support_feature(features, MfUltralightFeatureSupportPasswordAuth);
|
||||||
|
uint8_t pwd_num = mf_ultralight_get_pwd_page_num(data->type);
|
||||||
|
const uint8_t zero_pwd[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
|
if(pwd_supported && !memcmp(data->page[pwd_num].data, zero_pwd, sizeof(zero_pwd))) {
|
||||||
|
data->pages_read -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
NfcListener* mfu_listener = nfc_listener_alloc(listener, NfcProtocolMfUltralight, data);
|
||||||
|
|
||||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||||
|
|
||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
nfc_listener_free(mfu_listener);
|
nfc_listener_free(mfu_listener);
|
||||||
@@ -259,8 +270,8 @@ MU_TEST(ntag_213_locked_reader) {
|
|||||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||||
|
|
||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
nfc_listener_free(mfu_listener);
|
nfc_listener_free(mfu_listener);
|
||||||
@@ -297,8 +308,8 @@ static void mf_ultralight_write() {
|
|||||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||||
|
|
||||||
// Initial read
|
// Initial read
|
||||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
mu_assert(
|
mu_assert(
|
||||||
mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)),
|
mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)),
|
||||||
@@ -310,13 +321,13 @@ static void mf_ultralight_write() {
|
|||||||
FURI_LOG_D(TAG, "Writing page %d", i);
|
FURI_LOG_D(TAG, "Writing page %d", i);
|
||||||
furi_hal_random_fill_buf(page.data, sizeof(MfUltralightPage));
|
furi_hal_random_fill_buf(page.data, sizeof(MfUltralightPage));
|
||||||
mfu_data->page[i] = page;
|
mfu_data->page[i] = page;
|
||||||
error = mf_ultralight_poller_write_page(poller, i, &page);
|
error = mf_ultralight_poller_sync_write_page(poller, i, &page);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_write_page() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_write_page() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verification read
|
// Verification read
|
||||||
error = mf_ultralight_poller_read_card(poller, mfu_data);
|
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||||
|
|
||||||
nfc_listener_stop(mfu_listener);
|
nfc_listener_stop(mfu_listener);
|
||||||
const MfUltralightData* mfu_listener_data =
|
const MfUltralightData* mfu_listener_data =
|
||||||
@@ -344,7 +355,7 @@ static void mf_classic_reader() {
|
|||||||
MfClassicBlock block = {};
|
MfClassicBlock block = {};
|
||||||
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
||||||
|
|
||||||
mf_classic_poller_read_block(poller, 0, &key, MfClassicKeyTypeA, &block);
|
mf_classic_poller_sync_read_block(poller, 0, &key, MfClassicKeyTypeA, &block);
|
||||||
|
|
||||||
nfc_listener_stop(mfc_listener);
|
nfc_listener_stop(mfc_listener);
|
||||||
nfc_listener_free(mfc_listener);
|
nfc_listener_free(mfc_listener);
|
||||||
@@ -372,8 +383,8 @@ static void mf_classic_write() {
|
|||||||
furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock));
|
furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock));
|
||||||
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
||||||
|
|
||||||
mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||||
mf_classic_poller_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
|
mf_classic_poller_sync_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
|
||||||
|
|
||||||
nfc_listener_stop(mfc_listener);
|
nfc_listener_stop(mfc_listener);
|
||||||
nfc_listener_free(mfc_listener);
|
nfc_listener_free(mfc_listener);
|
||||||
@@ -402,16 +413,18 @@ static void mf_classic_value_block() {
|
|||||||
mf_classic_value_to_block(value, 1, &block_write);
|
mf_classic_value_to_block(value, 1, &block_write);
|
||||||
|
|
||||||
MfClassicError error = MfClassicErrorNone;
|
MfClassicError error = MfClassicErrorNone;
|
||||||
error = mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
error = mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||||
mu_assert(error == MfClassicErrorNone, "Write failed");
|
mu_assert(error == MfClassicErrorNone, "Write failed");
|
||||||
|
|
||||||
int32_t data = 200;
|
int32_t data = 200;
|
||||||
int32_t new_value = 0;
|
int32_t new_value = 0;
|
||||||
error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value);
|
error =
|
||||||
|
mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value);
|
||||||
mu_assert(error == MfClassicErrorNone, "Value increment failed");
|
mu_assert(error == MfClassicErrorNone, "Value increment failed");
|
||||||
mu_assert(new_value == value + data, "Value not match");
|
mu_assert(new_value == value + data, "Value not match");
|
||||||
|
|
||||||
error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value);
|
error =
|
||||||
|
mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value);
|
||||||
mu_assert(error == MfClassicErrorNone, "Value decrement failed");
|
mu_assert(error == MfClassicErrorNone, "Value decrement failed");
|
||||||
mu_assert(new_value == value, "Value not match");
|
mu_assert(new_value == value, "Value not match");
|
||||||
|
|
||||||
|
|||||||
@@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
|||||||
return NfcErrorNone;
|
return NfcErrorNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NfcError nfc_felica_listener_set_sensf_res_data(
|
||||||
|
Nfc* instance,
|
||||||
|
const uint8_t* idm,
|
||||||
|
const uint8_t idm_len,
|
||||||
|
const uint8_t* pmm,
|
||||||
|
const uint8_t pmm_len) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(idm);
|
||||||
|
furi_assert(pmm);
|
||||||
|
furi_assert(idm_len == 8);
|
||||||
|
furi_assert(pmm_len == 8);
|
||||||
|
|
||||||
|
return NfcErrorNone;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Filetype: Flipper SubGhz Key File
|
||||||
|
Version: 1
|
||||||
|
Frequency: 433920000
|
||||||
|
Preset: FuriHalSubGhzPresetOok270Async
|
||||||
|
Protocol: Mastercode
|
||||||
|
Bit: 36
|
||||||
|
Key: 00 00 00 0B 7E 00 3C 08
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Filetype: Flipper SubGhz RAW File
|
||||||
|
Version: 1
|
||||||
|
Frequency: 433920000
|
||||||
|
Preset: FuriHalSubGhzPresetOok270Async
|
||||||
|
Protocol: RAW
|
||||||
|
RAW_Data: 10389 -66 405095 -102 207 -106 1165 -130 963739 -1232 899 -2250 2003 -1190 2017 -1202 911 -2256 2021 -1162 2045 -1134 2047 -1164 2047 -1138 2031 -1180 2039 -1182 949 -2190 995 -2214 961 -2228 963 -2198 963 -2214 977 -2212 975 -2210 975 -2208 971 -2200 963 -2210 993 -2184 2075 -1130 2051 -1142 2055 -1136 2047 -1178 965 -2236 933 -2220 975 -2184 999 -2222 967 -2208 969 -2214 979 -2202 2027 -1156 975 -2242 943 -16080 2023 -1162 967 -2220 2057 -1114 2061 -1124 1007 -2242 2025 -1134 2055 -1168 2017 -1138 2075 -1134 2053 -1136 2075 -1130 979 -2214 979 -2174 999 -2182 1001 -2204 977 -2206 1003 -2188 979 -2176 999 -2182 1009 -2176 1009 -2176 1001 -2212 2029 -1116 2091 -1102 2109 -1092 2095 -1126 1001 -2150 1011 -2180 1011 -2180 1009 -2178 1009 -2172 1009 -2166 1001 -2198 2065 -1136 975 -2220 971 -16018 2097 -1166 951 -2240 2009 -1186 2011 -1160 979 -2208 2035 -1134 2053 -1138 2061 -1158 2045 -1152 2029 -1152 2051 -1166 963 -2188 993 -2222 951 -2214 963 -2220 965 -2212 979 -2212 977 -2180 1003 -2202 965 -2218 975 -2216 967 -2188 2061 -1124 2083 -1126 2071 -1130 2059 -1134 993 -2188 979 -2240 947 -2204 979 -2214 971 -2214 973 -2210 971 -2206 2053 -1130 979 -2216 969 -16056 2053 -1134 1001 -2224 2021 -1150 2051 -1154 953 -2240 2045 -1146 2023 -1168 2033 -1144 2065 -1146 2055 -1130 2071 -1160 961 -2192 973 -2190 1005 -2214 975 -2206 967 -2206 975 -2206 967 -2208 975 -2212 967 -2212 979 -2218 977 -2178 2063 -1156 2035 -1160 2061 -1126 2065 -1130 981 -2186 1003 -2210 977 -2208 973 -2202 977 -2200 965 -2248 943 -2206 2039 -1190 941 -48536 65 -7254 263 -68 363 -102 131 -232 263 -264 751 -230 225 -822 397 -634 231 -268 263 -134 267 -64 867 -132 305 -138 67 -100 331 -98 891 -66 455 -66 531 -100 299 -134 897 -98 693 -132 291 -132 333 -98 337 -68 331
|
||||||
@@ -1,27 +1,31 @@
|
|||||||
#include "flipper.pb.h"
|
|
||||||
#include <core/check.h>
|
#include <core/check.h>
|
||||||
#include <core/record.h>
|
#include <core/record.h>
|
||||||
#include "pb_decode.h"
|
|
||||||
#include <rpc/rpc.h>
|
|
||||||
#include "rpc/rpc_i.h"
|
|
||||||
#include "storage.pb.h"
|
|
||||||
#include "storage/filesystem_api_defines.h"
|
|
||||||
#include "storage/storage.h"
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include "../minunit.h"
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <pb.h>
|
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <m-list.h>
|
|
||||||
#include <lib/toolbox/md5_calc.h>
|
|
||||||
#include <lib/toolbox/path.h>
|
|
||||||
#include <cli/cli.h>
|
|
||||||
#include <loader/loader.h>
|
|
||||||
#include <protobuf_version.h>
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <semphr.h>
|
#include <semphr.h>
|
||||||
|
|
||||||
|
#include <rpc/rpc.h>
|
||||||
|
#include <rpc/rpc_i.h>
|
||||||
|
#include <cli/cli.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <loader/loader.h>
|
||||||
|
#include <storage/filesystem_api_defines.h>
|
||||||
|
|
||||||
|
#include <lib/toolbox/md5_calc.h>
|
||||||
|
#include <lib/toolbox/path.h>
|
||||||
|
|
||||||
|
#include <m-list.h>
|
||||||
|
#include "../minunit.h"
|
||||||
|
|
||||||
|
#include <protobuf_version.h>
|
||||||
|
#include <pb.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <storage.pb.h>
|
||||||
|
#include <flipper.pb.h>
|
||||||
|
|
||||||
LIST_DEF(MsgList, PB_Main, M_POD_OPLIST)
|
LIST_DEF(MsgList, PB_Main, M_POD_OPLIST)
|
||||||
#define M_OPL_MsgList_t() LIST_OPLIST(MsgList)
|
#define M_OPL_MsgList_t() LIST_OPLIST(MsgList)
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ static bool write_file_13DA(Storage* storage, const char* path) {
|
|||||||
File* file = storage_file_alloc(storage);
|
File* file = storage_file_alloc(storage);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||||
result = storage_file_write(file, "13DA", 4) == 4;
|
result = (storage_file_write(file, "13DA", 4) == 4);
|
||||||
}
|
}
|
||||||
storage_file_close(file);
|
storage_file_close(file);
|
||||||
storage_file_free(file);
|
storage_file_free(file);
|
||||||
|
|||||||
@@ -115,6 +115,66 @@ MU_TEST(storage_file_open_close) {
|
|||||||
furi_record_close(RECORD_STORAGE);
|
furi_record_close(RECORD_STORAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool storage_file_read_write_test(File* file, uint8_t* data, size_t test_size) {
|
||||||
|
const char* filename = UNIT_TESTS_PATH("storage_chunk.test");
|
||||||
|
|
||||||
|
// fill with pattern
|
||||||
|
for(size_t i = 0; i < test_size; i++) {
|
||||||
|
data[i] = (i % 113);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
do {
|
||||||
|
if(!storage_file_open(file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break;
|
||||||
|
if(test_size != storage_file_write(file, data, test_size)) break;
|
||||||
|
storage_file_close(file);
|
||||||
|
|
||||||
|
// reset data
|
||||||
|
memset(data, 0, test_size);
|
||||||
|
|
||||||
|
if(!storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING)) break;
|
||||||
|
if(test_size != storage_file_read(file, data, test_size)) break;
|
||||||
|
storage_file_close(file);
|
||||||
|
|
||||||
|
// check that data is correct
|
||||||
|
for(size_t i = 0; i < test_size; i++) {
|
||||||
|
if(data[i] != (i % 113)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(storage_file_read_write_64k) {
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
File* file = storage_file_alloc(storage);
|
||||||
|
|
||||||
|
size_t size_1k = 1024;
|
||||||
|
size_t size_64k = size_1k + size_1k * 63;
|
||||||
|
size_t size_65k = size_64k + size_1k;
|
||||||
|
size_t size_max = size_65k + 8;
|
||||||
|
|
||||||
|
size_t max_ram_block = memmgr_heap_get_max_free_block();
|
||||||
|
|
||||||
|
if(max_ram_block < size_max) {
|
||||||
|
mu_warn("Not enough RAM for >64k block test");
|
||||||
|
} else {
|
||||||
|
uint8_t* data = malloc(size_max);
|
||||||
|
mu_check(storage_file_read_write_test(file, data, size_1k));
|
||||||
|
mu_check(storage_file_read_write_test(file, data, size_64k));
|
||||||
|
mu_check(storage_file_read_write_test(file, data, size_65k));
|
||||||
|
mu_check(storage_file_read_write_test(file, data, size_max));
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_file_free(file);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST_SUITE(storage_file) {
|
MU_TEST_SUITE(storage_file) {
|
||||||
storage_file_open_lock_setup();
|
storage_file_open_lock_setup();
|
||||||
MU_RUN_TEST(storage_file_open_close);
|
MU_RUN_TEST(storage_file_open_close);
|
||||||
@@ -122,6 +182,10 @@ MU_TEST_SUITE(storage_file) {
|
|||||||
storage_file_open_lock_teardown();
|
storage_file_open_lock_teardown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST_SUITE(storage_file_64k) {
|
||||||
|
MU_RUN_TEST(storage_file_read_write_64k);
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST(storage_dir_open_close) {
|
MU_TEST(storage_dir_open_close) {
|
||||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
File* file;
|
File* file;
|
||||||
@@ -640,6 +704,7 @@ MU_TEST_SUITE(test_md5_calc_suite) {
|
|||||||
|
|
||||||
int run_minunit_test_storage() {
|
int run_minunit_test_storage() {
|
||||||
MU_RUN_SUITE(storage_file);
|
MU_RUN_SUITE(storage_file);
|
||||||
|
MU_RUN_SUITE(storage_file_64k);
|
||||||
MU_RUN_SUITE(storage_dir);
|
MU_RUN_SUITE(storage_dir);
|
||||||
MU_RUN_SUITE(storage_rename);
|
MU_RUN_SUITE(storage_rename);
|
||||||
MU_RUN_SUITE(test_data_path);
|
MU_RUN_SUITE(test_data_path);
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
|
|||||||
furi_hal_subghz_set_frequency_and_path(433920000);
|
furi_hal_subghz_set_frequency_and_path(433920000);
|
||||||
|
|
||||||
if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) {
|
if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) {
|
||||||
|
mu_warn("SubGHZ transmission is prohibited");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,6 +653,13 @@ MU_TEST(subghz_decoder_kinggates_stylo4k_test) {
|
|||||||
"Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n");
|
"Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(subghz_decoder_mastercode_test) {
|
||||||
|
mu_assert(
|
||||||
|
subghz_decoder_test(
|
||||||
|
EXT_PATH("unit_tests/subghz/mastercode_raw.sub"), SUBGHZ_PROTOCOL_MASTERCODE_NAME),
|
||||||
|
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
//test encoders
|
//test encoders
|
||||||
MU_TEST(subghz_encoder_princeton_test) {
|
MU_TEST(subghz_encoder_princeton_test) {
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@@ -803,6 +811,12 @@ MU_TEST(subghz_encoder_dooya_test) {
|
|||||||
"Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n");
|
"Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(subghz_encoder_mastercode_test) {
|
||||||
|
mu_assert(
|
||||||
|
subghz_encoder_test(EXT_PATH("unit_tests/subghz/mastercode.sub")),
|
||||||
|
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_random_test) {
|
MU_TEST(subghz_random_test) {
|
||||||
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
||||||
}
|
}
|
||||||
@@ -853,6 +867,7 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_decoder_alutech_at_4n_test);
|
MU_RUN_TEST(subghz_decoder_alutech_at_4n_test);
|
||||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||||
|
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
||||||
|
|
||||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||||
MU_RUN_TEST(subghz_encoder_came_test);
|
MU_RUN_TEST(subghz_encoder_came_test);
|
||||||
@@ -879,6 +894,7 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_encoder_smc5326_test);
|
MU_RUN_TEST(subghz_encoder_smc5326_test);
|
||||||
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
||||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||||
|
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
||||||
|
|
||||||
MU_RUN_TEST(subghz_random_test);
|
MU_RUN_TEST(subghz_random_test);
|
||||||
subghz_test_deinit();
|
subghz_test_deinit();
|
||||||
|
|||||||
@@ -78,6 +78,16 @@ void minunit_print_fail(const char* str) {
|
|||||||
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
|
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void minunit_printf_warning(const char* format, ...) {
|
||||||
|
FuriString* str = furi_string_alloc();
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
furi_string_vprintf(str, format, args);
|
||||||
|
va_end(args);
|
||||||
|
printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str));
|
||||||
|
furi_string_free(str);
|
||||||
|
}
|
||||||
|
|
||||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||||
UNUSED(cli);
|
UNUSED(cli);
|
||||||
UNUSED(args);
|
UNUSED(args);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="USB Mouse",
|
name="USB Mouse",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="usb_mouse_app",
|
entry_point="usb_mouse_app",
|
||||||
cdefines=["APP_USB_MOUSE"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=60,
|
order=60,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="USB Test",
|
name="USB Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="usb_test_app",
|
entry_point="usb_test_app",
|
||||||
cdefines=["APP_USB_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=50,
|
order=50,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ App(
|
|||||||
name="Vibro Test",
|
name="Vibro Test",
|
||||||
apptype=FlipperAppType.DEBUG,
|
apptype=FlipperAppType.DEBUG,
|
||||||
entry_point="vibro_test_app",
|
entry_point="vibro_test_app",
|
||||||
cdefines=["APP_VIBRO_TEST"],
|
|
||||||
requires=["gui"],
|
requires=["gui"],
|
||||||
stack_size=1 * 1024,
|
stack_size=1 * 1024,
|
||||||
order=20,
|
order=20,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ App(
|
|||||||
entry_point="advanced_plugin1_ep",
|
entry_point="advanced_plugin1_ep",
|
||||||
requires=["example_advanced_plugins"],
|
requires=["example_advanced_plugins"],
|
||||||
sources=["plugin1.c"],
|
sources=["plugin1.c"],
|
||||||
|
fal_embedded=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
@@ -22,4 +23,5 @@ App(
|
|||||||
entry_point="advanced_plugin2_ep",
|
entry_point="advanced_plugin2_ep",
|
||||||
requires=["example_advanced_plugins"],
|
requires=["example_advanced_plugins"],
|
||||||
sources=["plugin2.c"],
|
sources=["plugin2.c"],
|
||||||
|
fal_embedded=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ int32_t example_advanced_plugins_app(void* p) {
|
|||||||
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
|
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) {
|
// For built-in .fals (fal_embedded==True), use APP_ASSETS_PATH
|
||||||
|
// Otherwise, use APP_DATA_PATH
|
||||||
|
if(plugin_manager_load_all(manager, APP_ASSETS_PATH("plugins")) !=
|
||||||
|
PluginManagerErrorNone) {
|
||||||
FURI_LOG_E(TAG, "Failed to load all libs");
|
FURI_LOG_E(TAG, "Failed to load all libs");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
uint16_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN);
|
size_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN);
|
||||||
if(storage_file_get_error(file) != FSE_OK) {
|
if(storage_file_get_error(file) != FSE_OK) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(uint16_t i = 0; i < read_count; i++) {
|
for(size_t i = 0; i < read_count; i++) {
|
||||||
if(buffer[i] == '\n') {
|
if(buffer[i] == '\n') {
|
||||||
uint32_t position = storage_file_tell(file);
|
uint32_t position = storage_file_tell(file);
|
||||||
if(storage_file_get_error(file) != FSE_OK) {
|
if(storage_file_get_error(file) != FSE_OK) {
|
||||||
|
|||||||
@@ -174,22 +174,21 @@ void ibutton_free(iButton* ibutton) {
|
|||||||
free(ibutton);
|
free(ibutton);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ibutton_load_key(iButton* ibutton) {
|
bool ibutton_load_key(iButton* ibutton, bool show_error) {
|
||||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
|
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
|
||||||
|
|
||||||
const bool success = ibutton_protocols_load(
|
const bool success = ibutton_protocols_load(
|
||||||
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
|
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
|
||||||
|
|
||||||
if(!success) {
|
if(success) {
|
||||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
FuriString* tmp = furi_string_alloc();
|
FuriString* tmp = furi_string_alloc();
|
||||||
|
|
||||||
path_extract_filename(ibutton->file_path, tmp, true);
|
path_extract_filename(ibutton->file_path, tmp, true);
|
||||||
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
|
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
|
||||||
|
|
||||||
furi_string_free(tmp);
|
furi_string_free(tmp);
|
||||||
|
} else if(show_error) {
|
||||||
|
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -210,7 +209,7 @@ bool ibutton_select_and_load_key(iButton* ibutton) {
|
|||||||
if(!dialog_file_browser_show(
|
if(!dialog_file_browser_show(
|
||||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
|
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
|
||||||
break;
|
break;
|
||||||
success = ibutton_load_key(ibutton);
|
success = ibutton_load_key(ibutton, true);
|
||||||
} while(!success);
|
} while(!success);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -283,7 +282,7 @@ int32_t ibutton_app(void* arg) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
furi_string_set(ibutton->file_path, (const char*)arg);
|
furi_string_set(ibutton->file_path, (const char*)arg);
|
||||||
key_loaded = ibutton_load_key(ibutton);
|
key_loaded = ibutton_load_key(ibutton, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ typedef enum {
|
|||||||
} iButtonNotificationMessage;
|
} iButtonNotificationMessage;
|
||||||
|
|
||||||
bool ibutton_select_and_load_key(iButton* ibutton);
|
bool ibutton_select_and_load_key(iButton* ibutton);
|
||||||
bool ibutton_load_key(iButton* ibutton);
|
bool ibutton_load_key(iButton* ibutton, bool show_error);
|
||||||
bool ibutton_save_key(iButton* ibutton);
|
bool ibutton_save_key(iButton* ibutton);
|
||||||
bool ibutton_delete_key(iButton* ibutton);
|
bool ibutton_delete_key(iButton* ibutton);
|
||||||
void ibutton_reset_key(iButton* ibutton);
|
void ibutton_reset_key(iButton* ibutton);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
|
|||||||
} else if(event.type == SceneManagerEventTypeBack) {
|
} else if(event.type == SceneManagerEventTypeBack) {
|
||||||
// User cancelled editing, reload the key from storage
|
// User cancelled editing, reload the key from storage
|
||||||
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
|
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
|
||||||
if(!ibutton_load_key(ibutton)) {
|
if(!ibutton_load_key(ibutton, true)) {
|
||||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
scene_manager, iButtonSceneStart);
|
scene_manager, iButtonSceneStart);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ void ibutton_scene_emulate_on_enter(void* context) {
|
|||||||
|
|
||||||
furi_string_printf(
|
furi_string_printf(
|
||||||
tmp,
|
tmp,
|
||||||
"%s\n[%s]",
|
"[%s]\n%s",
|
||||||
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name,
|
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)),
|
||||||
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)));
|
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name);
|
||||||
|
|
||||||
widget_add_text_box_element(
|
widget_add_text_box_element(
|
||||||
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
||||||
|
|
||||||
widget_add_string_multiline_element(
|
widget_add_string_multiline_element(
|
||||||
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
|
widget, 88, 5, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
|
||||||
|
|
||||||
ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
|
ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
|
||||||
ibutton_worker_emulate_start(ibutton->worker, key);
|
ibutton_worker_emulate_start(ibutton->worker, key);
|
||||||
|
|||||||
@@ -8,21 +8,19 @@ void ibutton_scene_info_on_enter(void* context) {
|
|||||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
||||||
|
|
||||||
FuriString* tmp = furi_string_alloc();
|
FuriString* tmp = furi_string_alloc();
|
||||||
|
FuriString* keynumber = furi_string_alloc();
|
||||||
|
|
||||||
|
ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber);
|
||||||
|
|
||||||
furi_string_printf(
|
furi_string_printf(
|
||||||
tmp,
|
tmp,
|
||||||
"\e#%s [%s]\e#",
|
"\e#%s\n[%s]\e#\n%s",
|
||||||
ibutton->key_name,
|
ibutton->key_name,
|
||||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
|
||||||
|
furi_string_get_cstr(keynumber));
|
||||||
|
|
||||||
widget_add_text_box_element(
|
widget_add_text_box_element(
|
||||||
widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
|
widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
|
||||||
|
|
||||||
furi_string_reset(tmp);
|
|
||||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
|
|
||||||
|
|
||||||
widget_add_string_multiline_element(
|
|
||||||
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
|
||||||
|
|
||||||
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
|
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
|
||||||
iButtonProtocolFeatureExtData) {
|
iButtonProtocolFeatureExtData) {
|
||||||
@@ -32,6 +30,7 @@ void ibutton_scene_info_on_enter(void* context) {
|
|||||||
|
|
||||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||||
furi_string_free(tmp);
|
furi_string_free(tmp);
|
||||||
|
furi_string_free(keynumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
|
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(event.event == iButtonCustomEventRpcLoadFile) {
|
if(event.event == iButtonCustomEventRpcLoadFile) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
if(ibutton_load_key(ibutton)) {
|
if(ibutton_load_key(ibutton, false)) {
|
||||||
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
||||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||||
|
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ void ibutton_scene_write_on_enter(void* context) {
|
|||||||
|
|
||||||
furi_string_printf(
|
furi_string_printf(
|
||||||
tmp,
|
tmp,
|
||||||
"%s\n[%s]",
|
"[%s]\n%s ",
|
||||||
ibutton->key_name,
|
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
|
||||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
ibutton->key_name);
|
||||||
|
|
||||||
widget_add_text_box_element(
|
widget_add_text_box_element(
|
||||||
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
||||||
|
|
||||||
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
|
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ void ibutton_scene_write_on_enter(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
widget_add_string_multiline_element(
|
widget_add_string_multiline_element(
|
||||||
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
|
widget, 88, 5, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
|
||||||
|
|
||||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ App(
|
|||||||
"!plugins",
|
"!plugins",
|
||||||
"!nfc_cli.c",
|
"!nfc_cli.c",
|
||||||
],
|
],
|
||||||
fap_libs=["assets"],
|
fap_libs=["assets", "mbedtls"],
|
||||||
fap_icon="icon.png",
|
fap_icon="icon.png",
|
||||||
fap_category="NFC",
|
fap_category="NFC",
|
||||||
)
|
)
|
||||||
@@ -56,6 +56,15 @@ App(
|
|||||||
sources=["plugins/supported_cards/troika.c"],
|
sources=["plugins/supported_cards/troika.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="social_moscow_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="social_moscow_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/social_moscow.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="plantain_parser",
|
appid="plantain_parser",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
@@ -74,6 +83,60 @@ App(
|
|||||||
sources=["plugins/supported_cards/two_cities.c"],
|
sources=["plugins/supported_cards/two_cities.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="umarsh_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="umarsh_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/umarsh.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="metromoney_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="metromoney_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/metromoney.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="kazan_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="kazan_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/kazan.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="aime_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="aime_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/aime.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="saflok_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="saflok_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/saflok.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="mykey_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="mykey_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/mykey.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="nfc_start",
|
appid="nfc_start",
|
||||||
targets=["f7"],
|
targets=["f7"],
|
||||||
|
|||||||
@@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) {
|
||||||
|
const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica);
|
||||||
|
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data);
|
||||||
|
nfc_listener_start(instance->listener, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||||
.features = NfcProtocolFeatureNone,
|
.features = NfcProtocolFeatureEmulateUid,
|
||||||
|
|
||||||
.scene_info =
|
.scene_info =
|
||||||
{
|
{
|
||||||
@@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
|||||||
},
|
},
|
||||||
.scene_emulate =
|
.scene_emulate =
|
||||||
{
|
{
|
||||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
.on_enter = nfc_scene_emulate_on_enter_felica,
|
||||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ enum {
|
|||||||
SubmenuIndexUnlock = SubmenuIndexCommonMax,
|
SubmenuIndexUnlock = SubmenuIndexCommonMax,
|
||||||
SubmenuIndexUnlockByReader,
|
SubmenuIndexUnlockByReader,
|
||||||
SubmenuIndexUnlockByPassword,
|
SubmenuIndexUnlockByPassword,
|
||||||
|
SubmenuIndexWrite,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
|
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
|
||||||
@@ -106,6 +107,15 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
|
|||||||
SubmenuIndexUnlock,
|
SubmenuIndexUnlock,
|
||||||
nfc_protocol_support_common_submenu_callback,
|
nfc_protocol_support_common_submenu_callback,
|
||||||
instance);
|
instance);
|
||||||
|
} else if(
|
||||||
|
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
|
||||||
|
data->type == MfUltralightTypeNTAG216) {
|
||||||
|
submenu_add_item(
|
||||||
|
submenu,
|
||||||
|
"Write",
|
||||||
|
SubmenuIndexWrite,
|
||||||
|
nfc_protocol_support_common_submenu_callback,
|
||||||
|
instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +156,9 @@ static bool
|
|||||||
if(event == SubmenuIndexUnlock) {
|
if(event == SubmenuIndexUnlock) {
|
||||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||||
return true;
|
return true;
|
||||||
|
} else if(event == SubmenuIndexWrite) {
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
|
|||||||
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
|
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
|
||||||
furi_string_set(
|
furi_string_set(
|
||||||
temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
|
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_text_box_element(
|
widget_add_text_box_element(
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, v
|
|||||||
NfcApp* instance = context;
|
NfcApp* instance = context;
|
||||||
const St25tbPollerEvent* st25tb_event = event.event_data;
|
const St25tbPollerEvent* st25tb_event = event.event_data;
|
||||||
|
|
||||||
if(st25tb_event->type == St25tbPollerEventTypeReady) {
|
if(st25tb_event->type == St25tbPollerEventTypeRequestMode) {
|
||||||
|
st25tb_event->data->mode_request.mode = St25tbPollerModeRead;
|
||||||
|
} else if(st25tb_event->type == St25tbPollerEventTypeSuccess) {
|
||||||
nfc_device_set_data(
|
nfc_device_set_data(
|
||||||
instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
|
instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
|
||||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||||
|
|||||||
@@ -400,15 +400,16 @@ bool nfc_load_from_file_select(NfcApp* instance) {
|
|||||||
browser_options.base_path = NFC_APP_FOLDER;
|
browser_options.base_path = NFC_APP_FOLDER;
|
||||||
browser_options.hide_dot_files = true;
|
browser_options.hide_dot_files = true;
|
||||||
|
|
||||||
// Input events and views are managed by file_browser
|
bool success = false;
|
||||||
bool result = dialog_file_browser_show(
|
do {
|
||||||
instance->dialogs, instance->file_path, instance->file_path, &browser_options);
|
// Input events and views are managed by file_browser
|
||||||
|
if(!dialog_file_browser_show(
|
||||||
|
instance->dialogs, instance->file_path, instance->file_path, &browser_options))
|
||||||
|
break;
|
||||||
|
success = nfc_load_file(instance, instance->file_path, true);
|
||||||
|
} while(!success);
|
||||||
|
|
||||||
if(result) {
|
return success;
|
||||||
result = nfc_load_file(instance, instance->file_path, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_show_loading_popup(void* context, bool show) {
|
void nfc_show_loading_popup(void* context, bool show) {
|
||||||
|
|||||||
169
applications/main/nfc/plugins/supported_cards/aime.c
Normal file
169
applications/main/nfc/plugins/supported_cards/aime.c
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "nfc_supported_card_plugin.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 "Aime"
|
||||||
|
|
||||||
|
static const uint64_t aime_key = 0x574343467632;
|
||||||
|
|
||||||
|
bool aime_verify(Nfc* nfc) {
|
||||||
|
bool verified = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t verify_sector = 0;
|
||||||
|
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||||
|
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
|
||||||
|
|
||||||
|
MfClassicKey key = {};
|
||||||
|
nfc_util_num2bytes(aime_key, COUNT_OF(key.data), key.data);
|
||||||
|
|
||||||
|
MfClassicAuthContext auth_ctx = {};
|
||||||
|
MfClassicError error =
|
||||||
|
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||||
|
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
verified = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool aime_read(Nfc* nfc, NfcDevice* device) {
|
||||||
|
furi_assert(nfc);
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
bool is_read = false;
|
||||||
|
|
||||||
|
MfClassicData* data = mf_classic_alloc();
|
||||||
|
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MfClassicType type = MfClassicType1k;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
|
data->type = type;
|
||||||
|
MfClassicDeviceKeys keys = {};
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_a_mask, i);
|
||||||
|
nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
is_read = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
mf_classic_free(data);
|
||||||
|
|
||||||
|
return is_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool aime_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// verify key
|
||||||
|
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
|
||||||
|
uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6);
|
||||||
|
if(key != aime_key) break;
|
||||||
|
|
||||||
|
// Aime Magic is stored at block 1, starts from byte 0, len 4 bytes
|
||||||
|
const uint8_t* aime_magic = &data->block[1].data[0];
|
||||||
|
|
||||||
|
// verify aime magic
|
||||||
|
if(aime_magic[0] != 'S' || aime_magic[1] != 'B' || aime_magic[2] != 'S' ||
|
||||||
|
aime_magic[3] != 'D')
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Aime checksum is stored at block 1, starts from byte 13, len 3 bytes
|
||||||
|
// seems like only old games checks this? e.g., old versions of Chunithm
|
||||||
|
const uint8_t* aime_checksum = &data->block[1].data[13];
|
||||||
|
|
||||||
|
// Aime access code is stored as decimal hex representation in block 2, starts from byte 6, len 10 bytes
|
||||||
|
const uint8_t* aime_accesscode = &data->block[2].data[6];
|
||||||
|
|
||||||
|
char aime_accesscode_str[24 + 1];
|
||||||
|
snprintf(
|
||||||
|
aime_accesscode_str,
|
||||||
|
sizeof(aime_accesscode_str),
|
||||||
|
"%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x",
|
||||||
|
aime_accesscode[0],
|
||||||
|
aime_accesscode[1],
|
||||||
|
aime_accesscode[2],
|
||||||
|
aime_accesscode[3],
|
||||||
|
aime_accesscode[4],
|
||||||
|
aime_accesscode[5],
|
||||||
|
aime_accesscode[6],
|
||||||
|
aime_accesscode[7],
|
||||||
|
aime_accesscode[8],
|
||||||
|
aime_accesscode[9]);
|
||||||
|
|
||||||
|
// validate decimal hex representation
|
||||||
|
bool code_is_hex = true;
|
||||||
|
for(int i = 0; i < 24; i++) {
|
||||||
|
if(aime_accesscode_str[i] == ' ') continue;
|
||||||
|
if(aime_accesscode_str[i] < '0' || aime_accesscode_str[i] > '9') {
|
||||||
|
code_is_hex = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!code_is_hex) break;
|
||||||
|
|
||||||
|
// Note: Aime access code has some other self-check algorithms that are not public.
|
||||||
|
// This parser does not try to verify the number.
|
||||||
|
|
||||||
|
furi_string_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\e#Aime Card\nAccess Code: \n%s\nChecksum: %02X%02X%02X\n",
|
||||||
|
aime_accesscode_str,
|
||||||
|
aime_checksum[0],
|
||||||
|
aime_checksum[1],
|
||||||
|
aime_checksum[2]);
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin aime_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = aime_verify,
|
||||||
|
.read = aime_read,
|
||||||
|
.parse = aime_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor aime_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &aime_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* aime_plugin_ep() {
|
||||||
|
return &aime_plugin_descriptor;
|
||||||
|
}
|
||||||
360
applications/main/nfc/plugins/supported_cards/kazan.c
Normal file
360
applications/main/nfc/plugins/supported_cards/kazan.c
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
/*
|
||||||
|
* Parser for Kazan transport card (Kazan, Russia).
|
||||||
|
*
|
||||||
|
* 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/core_defines.h"
|
||||||
|
#include "core/log.h"
|
||||||
|
#include "core/string.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>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <furi_hal_rtc.h>
|
||||||
|
|
||||||
|
#define TAG "Kazan"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t a;
|
||||||
|
uint64_t b;
|
||||||
|
} MfClassicKeyPair;
|
||||||
|
|
||||||
|
static const MfClassicKeyPair kazan_1k_keys_standart[] = {
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xE954024EE754, .b = 0x0CD464CDC100},
|
||||||
|
{.a = 0xBC305FE2DA65, .b = 0xCF0EC6ACF2F9},
|
||||||
|
{.a = 0xF7A545095C49, .b = 0x6862FD600F78},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const MfClassicKeyPair kazan_1k_keys_old[] = {
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0x2058EAEE8446, .b = 0xCB9B23815F87},
|
||||||
|
{.a = 0x492F3744A1DC, .b = 0x6B770AADA274},
|
||||||
|
{.a = 0xF7A545095C49, .b = 0x6862FD600F78},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SubscriptionType {
|
||||||
|
SUBSCRIPTION_TYPE_UNKNOWN,
|
||||||
|
SUBSCRIPTION_TYPE_PURSE,
|
||||||
|
SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME,
|
||||||
|
SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SubscriptionType get_subscription_type(uint8_t value, FuriString* tariff_name) {
|
||||||
|
switch(value) {
|
||||||
|
case 0x51:
|
||||||
|
furi_string_printf(tariff_name, "Social. Adult");
|
||||||
|
return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME;
|
||||||
|
case 0x67:
|
||||||
|
furi_string_printf(tariff_name, "Ground electric transport. 1 month");
|
||||||
|
return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME;
|
||||||
|
case 0x0F:
|
||||||
|
furi_string_printf(tariff_name, "Underground only");
|
||||||
|
return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS;
|
||||||
|
case 0x6D:
|
||||||
|
furi_string_printf(tariff_name, "Tram. 60 minutes. Transfer. 10 trips");
|
||||||
|
return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS;
|
||||||
|
case 0x53:
|
||||||
|
furi_string_printf(tariff_name, "Standart purse");
|
||||||
|
return SUBSCRIPTION_TYPE_PURSE;
|
||||||
|
default:
|
||||||
|
furi_string_printf(tariff_name, "Unknown");
|
||||||
|
return SUBSCRIPTION_TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kazan_verify(Nfc* nfc) {
|
||||||
|
bool verified = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t verification_sector_number = 10;
|
||||||
|
const uint8_t verification_block_number =
|
||||||
|
mf_classic_get_first_block_num_of_sector(verification_sector_number) + 1;
|
||||||
|
FURI_LOG_D(TAG, "Verifying sector %u", verification_sector_number);
|
||||||
|
|
||||||
|
MfClassicKey key = {0};
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
kazan_1k_keys_standart[verification_sector_number].a, COUNT_OF(key.data), key.data);
|
||||||
|
|
||||||
|
MfClassicAuthContext auth_context;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_auth(
|
||||||
|
nfc, verification_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", verification_block_number, error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
verified = true;
|
||||||
|
} while(false);
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kazan_read(Nfc* nfc, NfcDevice* device) {
|
||||||
|
furi_assert(nfc);
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
bool is_read = false;
|
||||||
|
|
||||||
|
MfClassicData* data = mf_classic_alloc();
|
||||||
|
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MfClassicType type = MfClassicTypeMini;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
|
data->type = type;
|
||||||
|
if(type != MfClassicType1k) break;
|
||||||
|
|
||||||
|
MfClassicDeviceKeys keys = {
|
||||||
|
.key_a_mask = 0,
|
||||||
|
.key_b_mask = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
MfClassicDeviceKeys keys_old = {
|
||||||
|
.key_a_mask = 0,
|
||||||
|
.key_b_mask = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
kazan_1k_keys_standart[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
kazan_1k_keys_old[i].a, sizeof(MfClassicKey), keys_old.key_a[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_a_mask, i);
|
||||||
|
FURI_BIT_SET(keys_old.key_a_mask, i);
|
||||||
|
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
kazan_1k_keys_standart[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
kazan_1k_keys_old[i].b, sizeof(MfClassicKey), keys_old.key_b[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
|
FURI_BIT_SET(keys_old.key_b_mask, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data: standart keys");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!mf_classic_is_card_read(data)) {
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys_old, data);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data: old keys");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
is_read = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
mf_classic_free(data);
|
||||||
|
|
||||||
|
return is_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t verification_sector_number = 10;
|
||||||
|
const uint8_t ticket_sector_number = 8;
|
||||||
|
const uint8_t balance_sector_number = 9;
|
||||||
|
|
||||||
|
// Verify keys
|
||||||
|
MfClassicKeyPair keys = {};
|
||||||
|
const MfClassicSectorTrailer* sec_tr =
|
||||||
|
mf_classic_get_sector_trailer_by_sector(data, verification_sector_number);
|
||||||
|
|
||||||
|
keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||||
|
|
||||||
|
if(keys.a != 0xF7A545095C49) {
|
||||||
|
FURI_LOG_D(TAG, "Parser: Failed to verify key a: %llu", keys.a);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data
|
||||||
|
uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(ticket_sector_number);
|
||||||
|
|
||||||
|
const uint8_t* block_start_ptr = &data->block[start_block_num].data[6];
|
||||||
|
|
||||||
|
FuriString* tariff_name = furi_string_alloc();
|
||||||
|
enum SubscriptionType subscription_type =
|
||||||
|
get_subscription_type(block_start_ptr[0], tariff_name);
|
||||||
|
|
||||||
|
FuriHalRtcDateTime valid_from;
|
||||||
|
valid_from.year = 2000 + block_start_ptr[1];
|
||||||
|
valid_from.month = block_start_ptr[2];
|
||||||
|
valid_from.day = block_start_ptr[3];
|
||||||
|
|
||||||
|
FuriHalRtcDateTime valid_to;
|
||||||
|
valid_to.year = 2000 + block_start_ptr[4];
|
||||||
|
valid_to.month = block_start_ptr[5];
|
||||||
|
valid_to.day = block_start_ptr[6];
|
||||||
|
|
||||||
|
const uint8_t last_trip_block_number = 2;
|
||||||
|
block_start_ptr = &data->block[start_block_num + last_trip_block_number].data[1];
|
||||||
|
|
||||||
|
FuriHalRtcDateTime last_trip;
|
||||||
|
last_trip.year = 2000 + block_start_ptr[0];
|
||||||
|
last_trip.month = block_start_ptr[1];
|
||||||
|
last_trip.day = block_start_ptr[2];
|
||||||
|
last_trip.hour = block_start_ptr[3];
|
||||||
|
last_trip.minute = block_start_ptr[4];
|
||||||
|
bool is_last_trip_valid = (block_start_ptr[0] | block_start_ptr[1] | block_start_ptr[0]) &&
|
||||||
|
(last_trip.day < 32 && last_trip.month < 12 &&
|
||||||
|
last_trip.hour < 24 && last_trip.minute < 60);
|
||||||
|
|
||||||
|
start_block_num = mf_classic_get_first_block_num_of_sector(balance_sector_number);
|
||||||
|
block_start_ptr = &data->block[start_block_num].data[0];
|
||||||
|
|
||||||
|
const uint32_t trip_counter = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) |
|
||||||
|
(block_start_ptr[1] << 8) | (block_start_ptr[0]);
|
||||||
|
|
||||||
|
size_t uid_len = 0;
|
||||||
|
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||||
|
const uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]);
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data, "\e#Kazan transport card\nCard number: %lu\n", card_number);
|
||||||
|
|
||||||
|
if(subscription_type == SUBSCRIPTION_TYPE_PURSE) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Type: purse\nBalance: %lu RUR\nBalance valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u",
|
||||||
|
trip_counter,
|
||||||
|
valid_from.day,
|
||||||
|
valid_from.month,
|
||||||
|
valid_from.year,
|
||||||
|
valid_to.day,
|
||||||
|
valid_to.month,
|
||||||
|
valid_to.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Type: abonnement\nTariff: %s\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u",
|
||||||
|
furi_string_get_cstr(tariff_name),
|
||||||
|
trip_counter,
|
||||||
|
valid_from.day,
|
||||||
|
valid_from.month,
|
||||||
|
valid_from.year,
|
||||||
|
valid_to.day,
|
||||||
|
valid_to.month,
|
||||||
|
valid_to.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT_BY_TIME) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Type: abonnement\nTariff: %s\nTotal valid time: %lu days\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u",
|
||||||
|
furi_string_get_cstr(tariff_name),
|
||||||
|
trip_counter,
|
||||||
|
valid_from.day,
|
||||||
|
valid_from.month,
|
||||||
|
valid_from.year,
|
||||||
|
valid_to.day,
|
||||||
|
valid_to.month,
|
||||||
|
valid_to.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscription_type == SUBSCRIPTION_TYPE_UNKNOWN) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Type: unknown\nTariff: %s\nCounter: %lu\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u",
|
||||||
|
furi_string_get_cstr(tariff_name),
|
||||||
|
trip_counter,
|
||||||
|
valid_from.day,
|
||||||
|
valid_from.month,
|
||||||
|
valid_from.year,
|
||||||
|
valid_to.day,
|
||||||
|
valid_to.month,
|
||||||
|
valid_to.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_last_trip_valid) {
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\nLast trip: %02u.%02u.%u at %02u:%02u",
|
||||||
|
last_trip.day,
|
||||||
|
last_trip.month,
|
||||||
|
last_trip.year,
|
||||||
|
last_trip.hour,
|
||||||
|
last_trip.minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin kazan_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = kazan_verify,
|
||||||
|
.read = kazan_read,
|
||||||
|
.parse = kazan_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor kazan_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &kazan_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* kazan_plugin_ep() {
|
||||||
|
return &kazan_plugin_descriptor;
|
||||||
|
}
|
||||||
191
applications/main/nfc/plugins/supported_cards/metromoney.c
Normal file
191
applications/main/nfc/plugins/supported_cards/metromoney.c
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* Parser for Metromoney card (Georgia).
|
||||||
|
*
|
||||||
|
* 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 "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>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define TAG "Metromoney"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t a;
|
||||||
|
uint64_t b;
|
||||||
|
} MfClassicKeyPair;
|
||||||
|
|
||||||
|
static const MfClassicKeyPair metromoney_1k_keys[] = {
|
||||||
|
{.a = 0x2803BCB0C7E1, .b = 0x4FA9EB49F75E},
|
||||||
|
{.a = 0x9C616585E26D, .b = 0xD1C71E590D16},
|
||||||
|
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||||
|
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||||
|
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||||
|
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0x112233445566, .b = 0x361A62F35BC9},
|
||||||
|
{.a = 0x112233445566, .b = 0x361A62F35BC9},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool metromoney_verify(Nfc* nfc) {
|
||||||
|
bool verified = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t ticket_sector_number = 1;
|
||||||
|
const uint8_t ticket_block_number =
|
||||||
|
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
|
||||||
|
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
|
||||||
|
|
||||||
|
MfClassicKey key = {0};
|
||||||
|
nfc_util_num2bytes(
|
||||||
|
metromoney_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data);
|
||||||
|
|
||||||
|
MfClassicAuthContext auth_context;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_auth(
|
||||||
|
nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
verified = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool metromoney_read(Nfc* nfc, NfcDevice* device) {
|
||||||
|
furi_assert(nfc);
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
bool is_read = false;
|
||||||
|
|
||||||
|
MfClassicData* data = mf_classic_alloc();
|
||||||
|
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MfClassicType type = MfClassicTypeMini;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
|
data->type = type;
|
||||||
|
if(type != MfClassicType1k) break;
|
||||||
|
|
||||||
|
MfClassicDeviceKeys keys = {
|
||||||
|
.key_a_mask = 0,
|
||||||
|
.key_b_mask = 0,
|
||||||
|
};
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
nfc_util_num2bytes(metromoney_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_a_mask, i);
|
||||||
|
nfc_util_num2bytes(metromoney_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
is_read = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
mf_classic_free(data);
|
||||||
|
|
||||||
|
return is_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Verify key
|
||||||
|
const uint8_t ticket_sector_number = 1;
|
||||||
|
const uint8_t ticket_block_number = 1;
|
||||||
|
|
||||||
|
const MfClassicSectorTrailer* sec_tr =
|
||||||
|
mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
|
||||||
|
|
||||||
|
const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||||
|
if(key != metromoney_1k_keys[ticket_sector_number].a) break;
|
||||||
|
|
||||||
|
// Parse data
|
||||||
|
const uint8_t start_block_num =
|
||||||
|
mf_classic_get_first_block_num_of_sector(ticket_sector_number);
|
||||||
|
|
||||||
|
const uint8_t* block_start_ptr =
|
||||||
|
&data->block[start_block_num + ticket_block_number].data[0];
|
||||||
|
|
||||||
|
uint32_t balance = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) |
|
||||||
|
(block_start_ptr[1] << 8) | (block_start_ptr[0]);
|
||||||
|
|
||||||
|
uint32_t balance_lari = balance / 100;
|
||||||
|
uint8_t balance_tetri = balance % 100;
|
||||||
|
|
||||||
|
size_t uid_len = 0;
|
||||||
|
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||||
|
uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]);
|
||||||
|
|
||||||
|
furi_string_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\e#Metromoney\nCard number: %lu\nBalance: %lu.%02u GEL",
|
||||||
|
card_number,
|
||||||
|
balance_lari,
|
||||||
|
balance_tetri);
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin metromoney_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = metromoney_verify,
|
||||||
|
.read = metromoney_read,
|
||||||
|
.parse = metromoney_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &metromoney_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* metromoney_plugin_ep() {
|
||||||
|
return &metromoney_plugin_descriptor;
|
||||||
|
}
|
||||||
137
applications/main/nfc/plugins/supported_cards/mykey.c
Normal file
137
applications/main/nfc/plugins/supported_cards/mykey.c
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#include "nfc_supported_card_plugin.h"
|
||||||
|
#include <flipper_application/flipper_application.h>
|
||||||
|
#include <lib/nfc/protocols/st25tb/st25tb.h>
|
||||||
|
#include <nfc/nfc_device.h>
|
||||||
|
#include <nfc/helpers/nfc_util.h>
|
||||||
|
|
||||||
|
//Structures data of mykey card
|
||||||
|
enum {
|
||||||
|
MYKEY_BLOCK_KEY_ID = 0x07,
|
||||||
|
MYKEY_BLOCK_PRODUCTION_DATE = 0x08,
|
||||||
|
MYKEY_BLOCK_VENDOR_ID_1 = 0x18,
|
||||||
|
MYKEY_BLOCK_VENDOR_ID_2 = 0x19,
|
||||||
|
MYKEY_BLOCK_CURRENT_CREDIT = 0x21,
|
||||||
|
MYKEY_BLOCK_PREVIOUS_CREDIT = 0x23,
|
||||||
|
MYKEY_DEFAULT_VENDOR_ID = 0xFEDC0123,
|
||||||
|
MYKEY_DEFAULT_VENDOR_ID_1 = 0xFEDC,
|
||||||
|
MYKEY_DEFAULT_VENDOR_ID_2 = 0x0123,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LockIdStatusNone,
|
||||||
|
LockIdStatusActive,
|
||||||
|
} LockIdStatus;
|
||||||
|
|
||||||
|
/* Function to obtain the UID as a 32-bit */
|
||||||
|
uint32_t get_uid(const uint8_t uid[8]) {
|
||||||
|
return (uid[7] | (uid[6] << 8) | (uid[5] << 16) | (uid[4] << 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* OTP calculation (reverse block 6, incremental. 1,2,3, ecc.) */
|
||||||
|
uint32_t new_get_count_down_counter(uint32_t b6) {
|
||||||
|
return ~(b6 << 24 | (b6 & 0x0000FF00) << 8 | (b6 & 0x00FF0000) >> 8 | b6 >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function to check if the vendor is bound */
|
||||||
|
int get_is_bound(uint32_t vendor_id) {
|
||||||
|
return (vendor_id != MYKEY_DEFAULT_VENDOR_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MK = UID * VENDOR */
|
||||||
|
uint32_t get_master_key(uint32_t uid, uint32_t vendor_id) {
|
||||||
|
return uid * (vendor_id + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SK (Encryption key) = MK * OTP */
|
||||||
|
uint32_t get_encryption_key(uint32_t master_key, uint32_t count_down_counter) {
|
||||||
|
return master_key * (count_down_counter + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Encode or decode a MyKey block */
|
||||||
|
uint32_t encode_decode_block(uint32_t input) {
|
||||||
|
/*
|
||||||
|
* Swap all values using XOR
|
||||||
|
* 32 bit: 1111222233334444
|
||||||
|
*/
|
||||||
|
input ^= (input & 0x00C00000) << 6 | (input & 0x0000C000) << 12 | (input & 0x000000C0) << 18 |
|
||||||
|
(input & 0x000C0000) >> 6 | (input & 0x00030000) >> 12 | (input & 0x00000300) >> 6;
|
||||||
|
input ^= (input & 0x30000000) >> 6 | (input & 0x0C000000) >> 12 | (input & 0x03000000) >> 18 |
|
||||||
|
(input & 0x00003000) << 6 | (input & 0x00000030) << 12 | (input & 0x0000000C) << 6;
|
||||||
|
input ^= (input & 0x00C00000) << 6 | (input & 0x0000C000) << 12 | (input & 0x000000C0) << 18 |
|
||||||
|
(input & 0x000C0000) >> 6 | (input & 0x00030000) >> 12 | (input & 0x00000300) >> 6;
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t get_block(uint32_t block) {
|
||||||
|
return encode_decode_block(__bswap32(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t get_xored_block(uint32_t block, uint32_t key) {
|
||||||
|
return encode_decode_block(__bswap32(block) ^ key);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t get_vendor(uint32_t b1, uint32_t b2) {
|
||||||
|
return b1 << 16 | (b2 & 0x0000FFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
furi_assert(parsed_data);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
//Get data
|
||||||
|
const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
|
||||||
|
|
||||||
|
//Calc data
|
||||||
|
uint32_t _uid = get_uid(data->uid);
|
||||||
|
uint32_t _count_down_counter_new = new_get_count_down_counter(__bswap32(data->blocks[6]));
|
||||||
|
uint32_t _vendor_id = get_vendor(
|
||||||
|
get_block(data->blocks[MYKEY_BLOCK_VENDOR_ID_1]),
|
||||||
|
get_block(data->blocks[MYKEY_BLOCK_VENDOR_ID_2]));
|
||||||
|
uint32_t _master_key = get_master_key(_uid, _vendor_id);
|
||||||
|
uint32_t _encryption_key = get_encryption_key(_master_key, _count_down_counter_new);
|
||||||
|
uint16_t credit =
|
||||||
|
get_xored_block(data->blocks[MYKEY_BLOCK_CURRENT_CREDIT], _encryption_key);
|
||||||
|
uint16_t _previous_credit = get_block(data->blocks[MYKEY_BLOCK_PREVIOUS_CREDIT]);
|
||||||
|
bool _is_bound = get_is_bound(_vendor_id);
|
||||||
|
|
||||||
|
//parse data
|
||||||
|
furi_string_cat_printf(parsed_data, "\e#MyKey Card\n");
|
||||||
|
furi_string_cat_printf(parsed_data, "UID: %08lX\n", _uid);
|
||||||
|
furi_string_cat_printf(parsed_data, "Vendor ID: %08lX\n", _vendor_id);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data, "Current Credit: %d.%02d E \n", credit / 100, credit % 100);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Previus Credit: %d.%02d E \n",
|
||||||
|
_previous_credit / 100,
|
||||||
|
_previous_credit % 100);
|
||||||
|
furi_string_cat_printf(parsed_data, "Is Bound: %s\n", _is_bound ? "yes" : "no");
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin mykey_plugin = {
|
||||||
|
.protocol = NfcProtocolSt25tb,
|
||||||
|
.verify = NULL,
|
||||||
|
.read = NULL,
|
||||||
|
.parse = mykey_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor mykey_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &mykey_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* mykey_plugin_ep() {
|
||||||
|
return &mykey_plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include <nfc/nfc_device.h>
|
#include <nfc/nfc_device.h>
|
||||||
#include <nfc/helpers/nfc_util.h>
|
#include <nfc/helpers/nfc_util.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
|
||||||
#define TAG "Plantain"
|
#define TAG "Plantain"
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
|
|||||||
|
|
||||||
MfClassicAuthContext auth_context;
|
MfClassicAuthContext auth_context;
|
||||||
MfClassicError error =
|
MfClassicError error =
|
||||||
mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||||
break;
|
break;
|
||||||
@@ -119,7 +119,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
MfClassicType type = MfClassicTypeMini;
|
MfClassicType type = MfClassicTypeMini;
|
||||||
MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
if(error != MfClassicErrorNone) break;
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
data->type = type;
|
data->type = type;
|
||||||
@@ -134,7 +134,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
|||||||
FURI_BIT_SET(keys.key_b_mask, i);
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
error = mf_classic_poller_read(nfc, &keys, data);
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
FURI_LOG_W(TAG, "Failed to read data");
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
break;
|
break;
|
||||||
|
|||||||
174
applications/main/nfc/plugins/supported_cards/saflok.c
Normal file
174
applications/main/nfc/plugins/supported_cards/saflok.c
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// From: https://gitee.com/jadenwu/Saflok_KDF/blob/master/saflok.c
|
||||||
|
// KDF published and reverse engineered by Jaden Wu
|
||||||
|
// FZ plugin by @noproto
|
||||||
|
|
||||||
|
#include "nfc_supported_card_plugin.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>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define TAG "Saflok"
|
||||||
|
#define MAGIC_TABLE_SIZE 192
|
||||||
|
#define KEY_LENGTH 6
|
||||||
|
#define UID_LENGTH 4
|
||||||
|
#define CHECK_SECTOR 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t a;
|
||||||
|
uint64_t b;
|
||||||
|
} MfClassicKeyPair;
|
||||||
|
|
||||||
|
static MfClassicKeyPair saflok_1k_keys[] = {
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 000
|
||||||
|
{.a = 0x2a2c13cc242a, .b = 0xffffffffffff}, // 001
|
||||||
|
{.a = 0xffffffffffff, .b = 0xffffffffffff}, // 002
|
||||||
|
{.a = 0xffffffffffff, .b = 0xffffffffffff}, // 003
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 004
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 005
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 006
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 007
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 008
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 009
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 010
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 011
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 012
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 013
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 014
|
||||||
|
{.a = 0x000000000000, .b = 0xffffffffffff}, // 015
|
||||||
|
};
|
||||||
|
|
||||||
|
void generate_saflok_key(const uint8_t* uid, uint8_t* key) {
|
||||||
|
static const uint8_t magic_table[MAGIC_TABLE_SIZE] = {
|
||||||
|
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xF0, 0x57, 0xB3, 0x9E, 0xE3, 0xD8, 0x00, 0x00, 0xAA,
|
||||||
|
0x00, 0x00, 0x00, 0x96, 0x9D, 0x95, 0x4A, 0xC1, 0x57, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00,
|
||||||
|
0x8F, 0x43, 0x58, 0x0D, 0x2C, 0x9D, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0xE0,
|
||||||
|
0x05, 0x0C, 0x43, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x34, 0x1B, 0x15, 0xA6, 0x90, 0xCC,
|
||||||
|
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x89, 0x58, 0x56, 0x12, 0xE7, 0x1B, 0x00, 0x00, 0xAA,
|
||||||
|
0x00, 0x00, 0x00, 0xBB, 0x74, 0xB0, 0x95, 0x36, 0x58, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00,
|
||||||
|
0xFB, 0x97, 0xF8, 0x4B, 0x5B, 0x74, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xC9, 0xD1, 0x88,
|
||||||
|
0x35, 0x9F, 0x92, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x8F, 0x92, 0xE9, 0x7F, 0x58, 0x97,
|
||||||
|
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x16, 0x6C, 0xA2, 0xB0, 0x9F, 0xD1, 0x00, 0x00, 0xAA,
|
||||||
|
0x00, 0x00, 0x00, 0x27, 0xDD, 0x93, 0x10, 0x1C, 0x6C, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00,
|
||||||
|
0xDA, 0x3E, 0x3F, 0xD6, 0x49, 0xDD, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x58, 0xDD, 0xED,
|
||||||
|
0x07, 0x8E, 0x3E, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x5C, 0xD0, 0x05, 0xCF, 0xD9, 0x07,
|
||||||
|
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x11, 0x8D, 0xD0, 0x01, 0x87, 0xD0};
|
||||||
|
|
||||||
|
uint8_t magic_byte = (uid[3] >> 4) + (uid[2] >> 4) + (uid[0] & 0x0F);
|
||||||
|
uint8_t magickal_index = (magic_byte & 0x0F) * 12 + 11;
|
||||||
|
|
||||||
|
uint8_t temp_key[KEY_LENGTH] = {magic_byte, uid[0], uid[1], uid[2], uid[3], magic_byte};
|
||||||
|
uint8_t carry_sum = 0;
|
||||||
|
|
||||||
|
for(int i = KEY_LENGTH - 1; i >= 0; i--, magickal_index--) {
|
||||||
|
uint16_t keysum = temp_key[i] + magic_table[magickal_index];
|
||||||
|
temp_key[i] = (keysum & 0xFF) + carry_sum;
|
||||||
|
carry_sum = keysum >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(key, temp_key, KEY_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool saflok_verify(Nfc* nfc) {
|
||||||
|
bool verified = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(CHECK_SECTOR);
|
||||||
|
FURI_LOG_D(TAG, "Saflok: Verifying sector %i", CHECK_SECTOR);
|
||||||
|
|
||||||
|
MfClassicKey key = {0};
|
||||||
|
nfc_util_num2bytes(saflok_1k_keys[CHECK_SECTOR].a, COUNT_OF(key.data), key.data);
|
||||||
|
|
||||||
|
MfClassicAuthContext auth_context;
|
||||||
|
MfClassicError error =
|
||||||
|
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_D(TAG, "Saflok: Failed to read block %u: %d", block_num, error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
verified = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool saflok_read(Nfc* nfc, NfcDevice* device) {
|
||||||
|
FURI_LOG_D(TAG, "Entering Saflok KDF");
|
||||||
|
|
||||||
|
furi_assert(nfc);
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
bool is_read = false;
|
||||||
|
|
||||||
|
MfClassicData* data = mf_classic_alloc();
|
||||||
|
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
do {
|
||||||
|
MfClassicType type = MfClassicType1k;
|
||||||
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
|
if(error != MfClassicErrorNone) break;
|
||||||
|
data->type = type;
|
||||||
|
|
||||||
|
size_t uid_len;
|
||||||
|
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||||
|
FURI_LOG_D(
|
||||||
|
TAG, "Saflok: UID identified: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]);
|
||||||
|
if(uid_len != UID_LENGTH) break;
|
||||||
|
|
||||||
|
uint8_t key[KEY_LENGTH];
|
||||||
|
generate_saflok_key(uid, key);
|
||||||
|
uint64_t num_key = nfc_util_bytes2num(key, KEY_LENGTH);
|
||||||
|
FURI_LOG_D(TAG, "Saflok: Key generated for UID: %012llX", num_key);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
if(saflok_1k_keys[i].a == 0x000000000000) {
|
||||||
|
saflok_1k_keys[i].a = num_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MfClassicDeviceKeys keys = {};
|
||||||
|
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||||
|
nfc_util_num2bytes(saflok_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_a_mask, i);
|
||||||
|
nfc_util_num2bytes(saflok_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||||
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
|
if(error != MfClassicErrorNone) {
|
||||||
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||||
|
|
||||||
|
is_read = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
mf_classic_free(data);
|
||||||
|
|
||||||
|
return is_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin saflok_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = saflok_verify,
|
||||||
|
.read = saflok_read,
|
||||||
|
// KDF mode
|
||||||
|
.parse = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor saflok_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &saflok_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* saflok_plugin_ep() {
|
||||||
|
return &saflok_plugin_descriptor;
|
||||||
|
}
|
||||||
1632
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
1632
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include <nfc/nfc_device.h>
|
#include <nfc/nfc_device.h>
|
||||||
#include <nfc/helpers/nfc_util.h>
|
#include <nfc/helpers/nfc_util.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
|
||||||
#define TAG "TwoCities"
|
#define TAG "TwoCities"
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ bool two_cities_verify(Nfc* nfc) {
|
|||||||
|
|
||||||
MfClassicAuthContext auth_ctx = {};
|
MfClassicAuthContext auth_ctx = {};
|
||||||
MfClassicError error =
|
MfClassicError error =
|
||||||
mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||||
break;
|
break;
|
||||||
@@ -72,7 +72,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
MfClassicType type = MfClassicTypeMini;
|
MfClassicType type = MfClassicTypeMini;
|
||||||
MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
|
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||||
if(error != MfClassicErrorNone) break;
|
if(error != MfClassicErrorNone) break;
|
||||||
|
|
||||||
data->type = type;
|
data->type = type;
|
||||||
@@ -84,7 +84,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
|
|||||||
FURI_BIT_SET(keys.key_b_mask, i);
|
FURI_BIT_SET(keys.key_b_mask, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
error = mf_classic_poller_read(nfc, &keys, data);
|
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||||
if(error != MfClassicErrorNone) {
|
if(error != MfClassicErrorNone) {
|
||||||
FURI_LOG_W(TAG, "Failed to read data");
|
FURI_LOG_W(TAG, "Failed to read data");
|
||||||
break;
|
break;
|
||||||
|
|||||||
155
applications/main/nfc/plugins/supported_cards/umarsh.c
Normal file
155
applications/main/nfc/plugins/supported_cards/umarsh.c
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Parser for Umarsh card (Russia).
|
||||||
|
*
|
||||||
|
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||||
|
* Thanks https://github.com/krolchonok for the provided dumps and their analysis
|
||||||
|
*
|
||||||
|
* Note: All meaningful data is stored in sectors 0, 8 and 12, reading data
|
||||||
|
* from which is possible only with the B key. The key B for these sectors
|
||||||
|
* is unique for each card. To get it, you should use a nested attack.
|
||||||
|
* More info about Umarsh cards: https://github.com/metrodroid/metrodroid/wiki/Umarsh
|
||||||
|
*
|
||||||
|
* 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/core_defines.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>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <furi_hal_rtc.h>
|
||||||
|
|
||||||
|
#define TAG "Umarsh"
|
||||||
|
|
||||||
|
bool parse_datetime(uint16_t date, FuriHalRtcDateTime* result) {
|
||||||
|
result->year = 2000 + (date >> 9);
|
||||||
|
result->month = date >> 5 & 0x0F;
|
||||||
|
result->day = date & 0x1F;
|
||||||
|
return (date != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
|
furi_assert(device);
|
||||||
|
|
||||||
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
|
||||||
|
bool parsed = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Verify card type
|
||||||
|
if(data->type != MfClassicType1k) break;
|
||||||
|
|
||||||
|
const uint8_t ticket_sector = 8;
|
||||||
|
|
||||||
|
const uint8_t ticket_sector_start_block_number =
|
||||||
|
mf_classic_get_first_block_num_of_sector(ticket_sector);
|
||||||
|
|
||||||
|
// Validate specific for Umarsh ticket sector header
|
||||||
|
const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0];
|
||||||
|
|
||||||
|
const uint32_t header_part_0 = nfc_util_bytes2num(block_start_ptr, 4);
|
||||||
|
const uint32_t header_part_1 = nfc_util_bytes2num(block_start_ptr + 4, 4);
|
||||||
|
if((header_part_0 + header_part_1) != 0xFFFFFFFF) break;
|
||||||
|
|
||||||
|
// Data parsing from block 1
|
||||||
|
block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0];
|
||||||
|
const uint16_t expiry_date = nfc_util_bytes2num(block_start_ptr + 1, 2);
|
||||||
|
const uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) |
|
||||||
|
(block_start_ptr[12] & 0x0F);
|
||||||
|
const uint8_t refill_counter = nfc_util_bytes2num(block_start_ptr + 7, 1);
|
||||||
|
const uint32_t card_number = nfc_util_bytes2num(block_start_ptr + 8, 4) & 0x3FFFFFFF;
|
||||||
|
|
||||||
|
if(card_number == 0) break;
|
||||||
|
|
||||||
|
// Data parsing from block 2
|
||||||
|
block_start_ptr = &data->block[ticket_sector_start_block_number + 2].data[0];
|
||||||
|
const uint16_t valid_to = nfc_util_bytes2num(block_start_ptr, 2);
|
||||||
|
const uint32_t terminal_number = nfc_util_bytes2num(block_start_ptr + 3, 3);
|
||||||
|
const uint16_t last_refill_date = nfc_util_bytes2num(block_start_ptr + 6, 2);
|
||||||
|
const uint16_t balance_rub = (nfc_util_bytes2num(block_start_ptr + 8, 2)) & 0x7FFF;
|
||||||
|
const uint8_t balance_kop = nfc_util_bytes2num(block_start_ptr + 10, 1) & 0x7F;
|
||||||
|
|
||||||
|
FuriHalRtcDateTime expiry_datetime;
|
||||||
|
bool is_expiry_datetime_valid = parse_datetime(expiry_date, &expiry_datetime);
|
||||||
|
|
||||||
|
FuriHalRtcDateTime valid_to_datetime;
|
||||||
|
bool is_valid_to_datetime_valid = parse_datetime(valid_to, &valid_to_datetime);
|
||||||
|
|
||||||
|
FuriHalRtcDateTime last_refill_datetime;
|
||||||
|
bool is_last_refill_datetime_valid =
|
||||||
|
parse_datetime(last_refill_date, &last_refill_datetime);
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\e#Umarsh\nCard number: %lu\nRegion: %02u\nTerminal number: %lu\nRefill counter: %u\nBalance: %u.%02u RUR",
|
||||||
|
card_number,
|
||||||
|
region_number,
|
||||||
|
terminal_number,
|
||||||
|
refill_counter,
|
||||||
|
balance_rub,
|
||||||
|
balance_kop);
|
||||||
|
|
||||||
|
if(is_expiry_datetime_valid)
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\nExpires: %02u.%02u.%u",
|
||||||
|
expiry_datetime.day,
|
||||||
|
expiry_datetime.month,
|
||||||
|
expiry_datetime.year);
|
||||||
|
if(is_valid_to_datetime_valid)
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\nValid to: %02u.%02u.%u",
|
||||||
|
valid_to_datetime.day,
|
||||||
|
valid_to_datetime.month,
|
||||||
|
valid_to_datetime.year);
|
||||||
|
if(is_last_refill_datetime_valid)
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"\nLast refill: %02u.%02u.%u",
|
||||||
|
last_refill_datetime.day,
|
||||||
|
last_refill_datetime.month,
|
||||||
|
last_refill_datetime.year);
|
||||||
|
|
||||||
|
parsed = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const NfcSupportedCardsPlugin umarsh_plugin = {
|
||||||
|
.protocol = NfcProtocolMfClassic,
|
||||||
|
.verify = NULL,
|
||||||
|
.read = NULL,
|
||||||
|
.parse = umarsh_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor umarsh_plugin_descriptor = {
|
||||||
|
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &umarsh_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* umarsh_plugin_ep() {
|
||||||
|
return &umarsh_plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -3823,3 +3823,8 @@ F72CD208FDF9
|
|||||||
2158E314C3DF
|
2158E314C3DF
|
||||||
# 1k WALDORF ASTORIA
|
# 1k WALDORF ASTORIA
|
||||||
011C6CF459E8
|
011C6CF459E8
|
||||||
|
# 1k HAWAII HOTEL
|
||||||
|
2CAD8A83DF28
|
||||||
|
555D8BBC2D3E
|
||||||
|
78DF1176C8FD
|
||||||
|
ADC169F922CB
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ ADD_SCENE(nfc, field, Field)
|
|||||||
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
|
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
|
||||||
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
|
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
|
||||||
|
|
||||||
|
ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite)
|
||||||
|
ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess)
|
||||||
|
ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail)
|
||||||
|
ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard)
|
||||||
ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
|
ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
|
||||||
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
|
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
|
||||||
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
|
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
if(event.event == NfcCustomEventWorkerExit) {
|
if(event.event == NfcCustomEventWorkerExit) {
|
||||||
if(instance->protocols_detected_num > 1) {
|
if(instance->protocols_detected_num > 1) {
|
||||||
|
notification_message(instance->notifications, &sequence_single_vibro);
|
||||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
|
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
|
||||||
} else {
|
} else {
|
||||||
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
|
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
|
|||||||
|
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
if(event.event == SubmenuIndexMfClassicKeys) {
|
if(event.event == SubmenuIndexMfClassicKeys) {
|
||||||
if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
|
||||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
|
|
||||||
} else {
|
|
||||||
scene_manager_previous_scene(instance->scene_manager);
|
|
||||||
}
|
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event.event == SubmenuIndexMfUltralightUnlock) {
|
} else if(event.event == SubmenuIndexMfUltralightUnlock) {
|
||||||
mf_ultralight_auth_reset(instance->mf_ul_auth);
|
mf_ultralight_auth_reset(instance->mf_ul_auth);
|
||||||
|
|||||||
@@ -52,11 +52,12 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
|
|||||||
|
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
nfc->protocols_detected[0] = nfc_device_get_protocol(nfc->nfc_device);
|
|
||||||
MfUltralightAuthType type = nfc->mf_ul_auth->type;
|
MfUltralightAuthType type = nfc->mf_ul_auth->type;
|
||||||
if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) {
|
if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) {
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
if(event.event == DialogExResultRight) {
|
if(event.event == DialogExResultRight) {
|
||||||
|
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
|
||||||
|
nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
|
||||||
dolphin_deed(DolphinDeedNfcRead);
|
dolphin_deed(DolphinDeedNfcRead);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
|||||||
119
applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c
Normal file
119
applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "../nfc_app_i.h"
|
||||||
|
|
||||||
|
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NfcSceneMfUltralightWriteStateCardSearch,
|
||||||
|
NfcSceneMfUltralightWriteStateCardFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(event.event_data);
|
||||||
|
furi_assert(event.protocol == NfcProtocolMfUltralight);
|
||||||
|
|
||||||
|
NfcCommand command = NfcCommandContinue;
|
||||||
|
NfcApp* instance = context;
|
||||||
|
MfUltralightPollerEvent* mfu_event = event.event_data;
|
||||||
|
|
||||||
|
if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) {
|
||||||
|
mfu_event->data->poller_mode = MfUltralightPollerModeWrite;
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
||||||
|
mfu_event->data->auth_context.skip_auth = true;
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) {
|
||||||
|
mfu_event->data->write_data =
|
||||||
|
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) {
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
|
||||||
|
command = NfcCommandStop;
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) {
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||||
|
command = NfcCommandStop;
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) {
|
||||||
|
command = NfcCommandStop;
|
||||||
|
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) {
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||||
|
command = NfcCommandStop;
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) {
|
||||||
|
Popup* popup = instance->popup;
|
||||||
|
popup_reset(popup);
|
||||||
|
uint32_t state =
|
||||||
|
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||||
|
|
||||||
|
if(state == NfcSceneMfUltralightWriteStateCardSearch) {
|
||||||
|
popup_set_text(
|
||||||
|
instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, 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);
|
||||||
|
popup_set_icon(popup, 12, 23, &A_Loading_24);
|
||||||
|
}
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_on_enter(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
dolphin_deed(DolphinDeedNfcEmulate);
|
||||||
|
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
instance->scene_manager,
|
||||||
|
NfcSceneMfUltralightWrite,
|
||||||
|
NfcSceneMfUltralightWriteStateCardSearch);
|
||||||
|
nfc_scene_mf_ultralight_write_setup_view(instance);
|
||||||
|
|
||||||
|
// Setup and start worker
|
||||||
|
FURI_LOG_D("WMFU", "Card searching...");
|
||||||
|
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
|
||||||
|
nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance);
|
||||||
|
|
||||||
|
nfc_blink_emulate_start(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == NfcCustomEventCardDetected) {
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
instance->scene_manager,
|
||||||
|
NfcSceneMfUltralightWrite,
|
||||||
|
NfcSceneMfUltralightWriteStateCardFound);
|
||||||
|
nfc_scene_mf_ultralight_write_setup_view(instance);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == NfcCustomEventWrongCard) {
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == NfcCustomEventPollerSuccess) {
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == NfcCustomEventPollerFailure) {
|
||||||
|
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_on_exit(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
|
||||||
|
nfc_poller_stop(instance->poller);
|
||||||
|
nfc_poller_free(instance->poller);
|
||||||
|
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
instance->scene_manager,
|
||||||
|
NfcSceneMfUltralightWrite,
|
||||||
|
NfcSceneMfUltralightWriteStateCardSearch);
|
||||||
|
// Clear view
|
||||||
|
popup_reset(instance->popup);
|
||||||
|
|
||||||
|
nfc_blink_stop(instance);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include "../nfc_app_i.h"
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_fail_widget_callback(
|
||||||
|
GuiButtonType result,
|
||||||
|
InputType type,
|
||||||
|
void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
if(type == InputTypeShort) {
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
Widget* widget = instance->widget;
|
||||||
|
|
||||||
|
notification_message(instance->notifications, &sequence_error);
|
||||||
|
|
||||||
|
widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
|
||||||
|
widget_add_string_element(
|
||||||
|
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
widget,
|
||||||
|
7,
|
||||||
|
17,
|
||||||
|
AlignLeft,
|
||||||
|
AlignTop,
|
||||||
|
FontSecondary,
|
||||||
|
"Card protected by\npassword, AUTH0\nor lock bits");
|
||||||
|
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
GuiButtonTypeLeft,
|
||||||
|
"Finish",
|
||||||
|
nfc_scene_mf_ultralight_write_fail_widget_callback,
|
||||||
|
instance);
|
||||||
|
|
||||||
|
// Setup and start worker
|
||||||
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) {
|
||||||
|
bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
|
||||||
|
uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu;
|
||||||
|
|
||||||
|
return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == GuiButtonTypeLeft) {
|
||||||
|
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeBack) {
|
||||||
|
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
|
||||||
|
widget_reset(instance->widget);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include "../nfc_app_i.h"
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_success_on_enter(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
dolphin_deed(DolphinDeedNfcSave);
|
||||||
|
|
||||||
|
notification_message(instance->notifications, &sequence_success);
|
||||||
|
|
||||||
|
Popup* popup = instance->popup;
|
||||||
|
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||||
|
popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
|
||||||
|
popup_set_timeout(popup, 1500);
|
||||||
|
popup_set_context(popup, instance);
|
||||||
|
popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback);
|
||||||
|
popup_enable_timeout(popup);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == NfcCustomEventViewExit) {
|
||||||
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
instance->scene_manager, NfcSceneSavedMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_write_success_on_exit(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
|
||||||
|
// Clear view
|
||||||
|
popup_reset(instance->popup);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include "../nfc_app_i.h"
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_wrong_card_widget_callback(
|
||||||
|
GuiButtonType result,
|
||||||
|
InputType type,
|
||||||
|
void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
if(type == InputTypeShort) {
|
||||||
|
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
Widget* widget = instance->widget;
|
||||||
|
|
||||||
|
notification_message(instance->notifications, &sequence_error);
|
||||||
|
|
||||||
|
widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||||
|
widget_add_string_element(
|
||||||
|
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
widget,
|
||||||
|
4,
|
||||||
|
17,
|
||||||
|
AlignLeft,
|
||||||
|
AlignTop,
|
||||||
|
FontSecondary,
|
||||||
|
"Card of the same\ntype should be\n presented");
|
||||||
|
//"Data management\nis only possible\nwith card of same type");
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
GuiButtonTypeLeft,
|
||||||
|
"Retry",
|
||||||
|
nfc_scene_mf_ultralight_wrong_card_widget_callback,
|
||||||
|
instance);
|
||||||
|
|
||||||
|
// Setup and start worker
|
||||||
|
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == GuiButtonTypeLeft) {
|
||||||
|
consumed = scene_manager_previous_scene(instance->scene_manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) {
|
||||||
|
NfcApp* instance = context;
|
||||||
|
|
||||||
|
widget_reset(instance->widget);
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ void nfc_scene_select_protocol_on_enter(void* context) {
|
|||||||
} else {
|
} else {
|
||||||
prefix = "Read as";
|
prefix = "Read as";
|
||||||
submenu_set_header(submenu, "Multi-protocol card");
|
submenu_set_header(submenu, "Multi-protocol card");
|
||||||
notification_message(instance->notifications, &sequence_single_vibro);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for(uint32_t i = 0; i < instance->protocols_detected_num; i++) {
|
for(uint32_t i = 0; i < instance->protocols_detected_num; i++) {
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ void dict_attack_reset(DictAttack* instance) {
|
|||||||
instance->view,
|
instance->view,
|
||||||
DictAttackViewModel * model,
|
DictAttackViewModel * model,
|
||||||
{
|
{
|
||||||
model->card_detected = false;
|
|
||||||
model->sectors_total = 0;
|
model->sectors_total = 0;
|
||||||
model->sectors_read = 0;
|
model->sectors_read = 0;
|
||||||
model->current_sector = 0;
|
model->current_sector = 0;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ void subghz_scene_radio_settings_on_enter(void* context) {
|
|||||||
|
|
||||||
item = variable_item_list_add(
|
item = variable_item_list_add(
|
||||||
variable_item_list,
|
variable_item_list,
|
||||||
"Time In Names",
|
"Protocol Names",
|
||||||
TIMESTAMP_NAMES_COUNT,
|
TIMESTAMP_NAMES_COUNT,
|
||||||
subghz_scene_receiver_config_set_timestamp_file_names,
|
subghz_scene_receiver_config_set_timestamp_file_names,
|
||||||
subghz);
|
subghz);
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
#define MAX_HISTORY 4
|
#define MAX_HISTORY 4
|
||||||
|
|
||||||
static const uint32_t subghz_frequency_list[] = {
|
static const uint32_t subghz_frequency_list[] = {
|
||||||
300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000, 309000000,
|
300000000, 302757000, 303875000, 303900000, 304250000, 307000000, 307500000, 307800000,
|
||||||
310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000, 314980000,
|
309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000,
|
||||||
315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000, 390000000,
|
314980000, 315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000,
|
||||||
418000000, 430000000, 431000000, 431500000, 433075000, 433220000, 433420000, 433657070,
|
390000000, 418000000, 430000000, 431000000, 431500000, 433075000, 433220000, 433420000,
|
||||||
433889000, 433920000, 434075000, 434176948, 434390000, 434420000, 434775000, 438900000,
|
433657070, 433889000, 433920000, 434075000, 434176948, 434390000, 434420000, 434775000,
|
||||||
440175000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000, 906400000,
|
438900000, 440175000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000,
|
||||||
915000000, 925000000, 928000000};
|
906400000, 915000000, 925000000, 928000000};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SubGhzFrequencyAnalyzerStatusIDLE,
|
SubGhzFrequencyAnalyzerStatusIDLE,
|
||||||
|
|||||||
Submodule applications/main/subghz_remote updated: fb508ea02e...2ea0fac185
@@ -7,7 +7,7 @@ App(
|
|||||||
icon="A_U2F_14",
|
icon="A_U2F_14",
|
||||||
order=80,
|
order=80,
|
||||||
resources="resources",
|
resources="resources",
|
||||||
fap_libs=["assets"],
|
fap_libs=["assets", "mbedtls"],
|
||||||
fap_category="USB",
|
fap_category="USB",
|
||||||
fap_icon="icon.png",
|
fap_icon="icon.png",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* hmac.c - HMAC
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017 Sergei Glushchenko
|
|
||||||
* Author: Sergei Glushchenko <gl.sergei@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is a part of U2F firmware for STM32
|
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*
|
|
||||||
* As additional permission under GNU GPL version 3 section 7, you may
|
|
||||||
* distribute non-source form of the Program without the copy of the
|
|
||||||
* GNU GPL normally required by section 4, provided you inform the
|
|
||||||
* recipients of GNU GPL by a written offer.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "sha256.h"
|
|
||||||
#include "hmac_sha256.h"
|
|
||||||
|
|
||||||
static void _hmac_sha256_init(const hmac_context* ctx) {
|
|
||||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
|
||||||
sha256_start(&context->sha_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_hmac_sha256_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) {
|
|
||||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
|
||||||
sha256_update(&context->sha_ctx, message, message_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _hmac_sha256_finish(const hmac_context* ctx, uint8_t* hash_result) {
|
|
||||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
|
||||||
sha256_finish(&context->sha_ctx, hash_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always
|
|
||||||
the same size as the hash result size. */
|
|
||||||
static void hmac_init(const hmac_context* ctx, const uint8_t* K) {
|
|
||||||
uint8_t* pad = ctx->tmp + 2 * ctx->result_size;
|
|
||||||
unsigned i;
|
|
||||||
for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x36;
|
|
||||||
for(; i < ctx->block_size; ++i) pad[i] = 0x36;
|
|
||||||
|
|
||||||
ctx->init_hash(ctx);
|
|
||||||
ctx->update_hash(ctx, pad, ctx->block_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hmac_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) {
|
|
||||||
ctx->update_hash(ctx, message, message_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hmac_finish(const hmac_context* ctx, const uint8_t* K, uint8_t* result) {
|
|
||||||
uint8_t* pad = ctx->tmp + 2 * ctx->result_size;
|
|
||||||
unsigned i;
|
|
||||||
for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x5c;
|
|
||||||
for(; i < ctx->block_size; ++i) pad[i] = 0x5c;
|
|
||||||
|
|
||||||
ctx->finish_hash(ctx, result);
|
|
||||||
|
|
||||||
ctx->init_hash(ctx);
|
|
||||||
ctx->update_hash(ctx, pad, ctx->block_size);
|
|
||||||
ctx->update_hash(ctx, result, ctx->result_size);
|
|
||||||
ctx->finish_hash(ctx, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K) {
|
|
||||||
ctx->hmac_ctx.init_hash = _hmac_sha256_init;
|
|
||||||
ctx->hmac_ctx.update_hash = _hmac_sha256_update;
|
|
||||||
ctx->hmac_ctx.finish_hash = _hmac_sha256_finish;
|
|
||||||
ctx->hmac_ctx.block_size = 64;
|
|
||||||
ctx->hmac_ctx.result_size = 32;
|
|
||||||
ctx->hmac_ctx.tmp = ctx->tmp;
|
|
||||||
hmac_init(&ctx->hmac_ctx, K);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmac_sha256_update(
|
|
||||||
const hmac_sha256_context* ctx,
|
|
||||||
const uint8_t* message,
|
|
||||||
unsigned message_size) {
|
|
||||||
hmac_update(&ctx->hmac_ctx, message, message_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result) {
|
|
||||||
hmac_finish(&ctx->hmac_ctx, K, hash_result);
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "sha256.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct hmac_context {
|
|
||||||
void (*init_hash)(const struct hmac_context* context);
|
|
||||||
void (*update_hash)(
|
|
||||||
const struct hmac_context* context,
|
|
||||||
const uint8_t* message,
|
|
||||||
unsigned message_size);
|
|
||||||
void (*finish_hash)(const struct hmac_context* context, uint8_t* hash_result);
|
|
||||||
unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */
|
|
||||||
unsigned result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */
|
|
||||||
uint8_t* tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */
|
|
||||||
} hmac_context;
|
|
||||||
|
|
||||||
typedef struct hmac_sha256_context {
|
|
||||||
hmac_context hmac_ctx;
|
|
||||||
sha256_context sha_ctx;
|
|
||||||
uint8_t tmp[32 * 2 + 64];
|
|
||||||
} hmac_sha256_context;
|
|
||||||
|
|
||||||
void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K);
|
|
||||||
|
|
||||||
void hmac_sha256_update(
|
|
||||||
const hmac_sha256_context* ctx,
|
|
||||||
const uint8_t* message,
|
|
||||||
unsigned message_size);
|
|
||||||
|
|
||||||
void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
#include <furi.h>
|
|
||||||
#include "u2f.h"
|
#include "u2f.h"
|
||||||
#include "u2f_hid.h"
|
#include "u2f_hid.h"
|
||||||
#include "u2f_data.h"
|
#include "u2f_data.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include <furi_hal_random.h>
|
#include <furi_hal_random.h>
|
||||||
#include <littlefs/lfs_util.h> // for lfs_tobe32
|
#include <littlefs/lfs_util.h> // for lfs_tobe32
|
||||||
|
|
||||||
#include "toolbox/sha256.h"
|
#include <mbedtls/sha256.h>
|
||||||
#include "hmac_sha256.h"
|
#include <mbedtls/md.h>
|
||||||
#include "micro-ecc/uECC.h"
|
#include <mbedtls/ecdsa.h>
|
||||||
|
#include <mbedtls/error.h>
|
||||||
|
|
||||||
#define TAG "U2f"
|
#define TAG "U2f"
|
||||||
#define WORKER_TAG TAG "Worker"
|
#define WORKER_TAG TAG "Worker"
|
||||||
|
|
||||||
|
#define MCHECK(expr) furi_check((expr) == 0)
|
||||||
|
|
||||||
#define U2F_CMD_REGISTER 0x01
|
#define U2F_CMD_REGISTER 0x01
|
||||||
#define U2F_CMD_AUTHENTICATE 0x02
|
#define U2F_CMD_AUTHENTICATE 0x02
|
||||||
#define U2F_CMD_VERSION 0x03
|
#define U2F_CMD_VERSION 0x03
|
||||||
@@ -25,16 +29,26 @@ typedef enum {
|
|||||||
0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing
|
0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing
|
||||||
} U2fAuthMode;
|
} U2fAuthMode;
|
||||||
|
|
||||||
|
#define U2F_HASH_SIZE 32
|
||||||
|
#define U2F_NONCE_SIZE 32
|
||||||
|
#define U2F_CHALLENGE_SIZE 32
|
||||||
|
#define U2F_APP_ID_SIZE 32
|
||||||
|
|
||||||
|
#define U2F_EC_KEY_SIZE 32
|
||||||
|
#define U2F_EC_BIGNUM_SIZE 32
|
||||||
|
#define U2F_EC_POINT_SIZE 65
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t format;
|
uint8_t format;
|
||||||
uint8_t xy[64];
|
uint8_t xy[64];
|
||||||
} __attribute__((packed)) U2fPubKey;
|
} FURI_PACKED U2fPubKey;
|
||||||
|
_Static_assert(sizeof(U2fPubKey) == U2F_EC_POINT_SIZE, "U2fPubKey size mismatch");
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t len;
|
uint8_t len;
|
||||||
uint8_t hash[32];
|
uint8_t hash[U2F_HASH_SIZE];
|
||||||
uint8_t nonce[32];
|
uint8_t nonce[U2F_NONCE_SIZE];
|
||||||
} __attribute__((packed)) U2fKeyHandle;
|
} FURI_PACKED U2fKeyHandle;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t cla;
|
uint8_t cla;
|
||||||
@@ -42,16 +56,16 @@ typedef struct {
|
|||||||
uint8_t p1;
|
uint8_t p1;
|
||||||
uint8_t p2;
|
uint8_t p2;
|
||||||
uint8_t len[3];
|
uint8_t len[3];
|
||||||
uint8_t challenge[32];
|
uint8_t challenge[U2F_CHALLENGE_SIZE];
|
||||||
uint8_t app_id[32];
|
uint8_t app_id[U2F_APP_ID_SIZE];
|
||||||
} __attribute__((packed)) U2fRegisterReq;
|
} FURI_PACKED U2fRegisterReq;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t reserved;
|
uint8_t reserved;
|
||||||
U2fPubKey pub_key;
|
U2fPubKey pub_key;
|
||||||
U2fKeyHandle key_handle;
|
U2fKeyHandle key_handle;
|
||||||
uint8_t cert[];
|
uint8_t cert[];
|
||||||
} __attribute__((packed)) U2fRegisterResp;
|
} FURI_PACKED U2fRegisterResp;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t cla;
|
uint8_t cla;
|
||||||
@@ -59,16 +73,16 @@ typedef struct {
|
|||||||
uint8_t p1;
|
uint8_t p1;
|
||||||
uint8_t p2;
|
uint8_t p2;
|
||||||
uint8_t len[3];
|
uint8_t len[3];
|
||||||
uint8_t challenge[32];
|
uint8_t challenge[U2F_CHALLENGE_SIZE];
|
||||||
uint8_t app_id[32];
|
uint8_t app_id[U2F_APP_ID_SIZE];
|
||||||
U2fKeyHandle key_handle;
|
U2fKeyHandle key_handle;
|
||||||
} __attribute__((packed)) U2fAuthReq;
|
} FURI_PACKED U2fAuthReq;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t user_present;
|
uint8_t user_present;
|
||||||
uint32_t counter;
|
uint32_t counter;
|
||||||
uint8_t signature[];
|
uint8_t signature[];
|
||||||
} __attribute__((packed)) U2fAuthResp;
|
} FURI_PACKED U2fAuthResp;
|
||||||
|
|
||||||
static const uint8_t ver_str[] = {"U2F_V2"};
|
static const uint8_t ver_str[] = {"U2F_V2"};
|
||||||
|
|
||||||
@@ -78,19 +92,20 @@ static const uint8_t state_user_missing[] = {0x69, 0x85};
|
|||||||
static const uint8_t state_wrong_data[] = {0x6A, 0x80};
|
static const uint8_t state_wrong_data[] = {0x6A, 0x80};
|
||||||
|
|
||||||
struct U2fData {
|
struct U2fData {
|
||||||
uint8_t device_key[32];
|
uint8_t device_key[U2F_EC_KEY_SIZE];
|
||||||
uint8_t cert_key[32];
|
uint8_t cert_key[U2F_EC_KEY_SIZE];
|
||||||
uint32_t counter;
|
uint32_t counter;
|
||||||
const struct uECC_Curve_t* p_curve;
|
|
||||||
bool ready;
|
bool ready;
|
||||||
bool user_present;
|
bool user_present;
|
||||||
U2fEvtCallback callback;
|
U2fEvtCallback callback;
|
||||||
void* context;
|
void* context;
|
||||||
|
mbedtls_ecp_group group;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int u2f_uecc_random(uint8_t* dest, unsigned size) {
|
static int u2f_uecc_random_cb(void* context, uint8_t* dest, unsigned size) {
|
||||||
|
UNUSED(context);
|
||||||
furi_hal_random_fill_buf(dest, size);
|
furi_hal_random_fill_buf(dest, size);
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
U2fData* u2f_alloc() {
|
U2fData* u2f_alloc() {
|
||||||
@@ -99,6 +114,7 @@ U2fData* u2f_alloc() {
|
|||||||
|
|
||||||
void u2f_free(U2fData* U2F) {
|
void u2f_free(U2fData* U2F) {
|
||||||
furi_assert(U2F);
|
furi_assert(U2F);
|
||||||
|
mbedtls_ecp_group_free(&U2F->group);
|
||||||
free(U2F);
|
free(U2F);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +145,8 @@ bool u2f_init(U2fData* U2F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
U2F->p_curve = uECC_secp256r1();
|
mbedtls_ecp_group_init(&U2F->group);
|
||||||
uECC_set_rng(u2f_uecc_random);
|
mbedtls_ecp_group_load(&U2F->group, MBEDTLS_ECP_DP_SECP256R1);
|
||||||
|
|
||||||
U2F->ready = true;
|
U2F->ready = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -171,21 +187,63 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) {
|
|||||||
der[0] = 0x30;
|
der[0] = 0x30;
|
||||||
|
|
||||||
uint8_t len = 2;
|
uint8_t len = 2;
|
||||||
len += u2f_der_encode_int(der + len, sig, 32);
|
len += u2f_der_encode_int(der + len, sig, U2F_HASH_SIZE);
|
||||||
len += u2f_der_encode_int(der + len, sig + 32, 32);
|
len += u2f_der_encode_int(der + len, sig + U2F_HASH_SIZE, U2F_HASH_SIZE);
|
||||||
|
|
||||||
der[1] = len - 2;
|
der[1] = len - 2;
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
u2f_ecc_sign(mbedtls_ecp_group* grp, const uint8_t* key, uint8_t* hash, uint8_t* signature) {
|
||||||
|
mbedtls_mpi r, s, d;
|
||||||
|
|
||||||
|
mbedtls_mpi_init(&r);
|
||||||
|
mbedtls_mpi_init(&s);
|
||||||
|
mbedtls_mpi_init(&d);
|
||||||
|
|
||||||
|
MCHECK(mbedtls_mpi_read_binary(&d, key, U2F_EC_KEY_SIZE));
|
||||||
|
MCHECK(mbedtls_ecdsa_sign(grp, &r, &s, &d, hash, U2F_HASH_SIZE, u2f_uecc_random_cb, NULL));
|
||||||
|
MCHECK(mbedtls_mpi_write_binary(&r, signature, U2F_EC_BIGNUM_SIZE));
|
||||||
|
MCHECK(mbedtls_mpi_write_binary(&s, signature + U2F_EC_BIGNUM_SIZE, U2F_EC_BIGNUM_SIZE));
|
||||||
|
|
||||||
|
mbedtls_mpi_free(&r);
|
||||||
|
mbedtls_mpi_free(&s);
|
||||||
|
mbedtls_mpi_free(&d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void u2f_ecc_compute_public_key(
|
||||||
|
mbedtls_ecp_group* grp,
|
||||||
|
const uint8_t* private_key,
|
||||||
|
U2fPubKey* public_key) {
|
||||||
|
mbedtls_ecp_point Q;
|
||||||
|
mbedtls_mpi d;
|
||||||
|
size_t olen;
|
||||||
|
|
||||||
|
mbedtls_ecp_point_init(&Q);
|
||||||
|
mbedtls_mpi_init(&d);
|
||||||
|
|
||||||
|
MCHECK(mbedtls_mpi_read_binary(&d, private_key, U2F_EC_KEY_SIZE));
|
||||||
|
MCHECK(mbedtls_ecp_mul(grp, &Q, &d, &grp->G, u2f_uecc_random_cb, NULL));
|
||||||
|
MCHECK(mbedtls_ecp_check_privkey(grp, &d));
|
||||||
|
|
||||||
|
MCHECK(mbedtls_ecp_point_write_binary(
|
||||||
|
grp, &Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (unsigned char*)public_key, sizeof(U2fPubKey)));
|
||||||
|
|
||||||
|
mbedtls_ecp_point_free(&Q);
|
||||||
|
mbedtls_mpi_free(&d);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
|
||||||
static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||||
U2fRegisterReq* req = (U2fRegisterReq*)buf;
|
U2fRegisterReq* req = (U2fRegisterReq*)buf;
|
||||||
U2fRegisterResp* resp = (U2fRegisterResp*)buf;
|
U2fRegisterResp* resp = (U2fRegisterResp*)buf;
|
||||||
U2fKeyHandle handle;
|
U2fKeyHandle handle;
|
||||||
uint8_t private[32];
|
uint8_t private[U2F_EC_KEY_SIZE];
|
||||||
U2fPubKey pub_key;
|
U2fPubKey pub_key;
|
||||||
uint8_t hash[32];
|
uint8_t hash[U2F_HASH_SIZE];
|
||||||
uint8_t signature[64];
|
uint8_t signature[U2F_EC_BIGNUM_SIZE * 2];
|
||||||
|
|
||||||
if(u2f_data_check(false) == false) {
|
if(u2f_data_check(false) == false) {
|
||||||
U2F->ready = false;
|
U2F->ready = false;
|
||||||
@@ -201,40 +259,54 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
|||||||
}
|
}
|
||||||
U2F->user_present = false;
|
U2F->user_present = false;
|
||||||
|
|
||||||
hmac_sha256_context hmac_ctx;
|
handle.len = U2F_HASH_SIZE * 2;
|
||||||
sha256_context sha_ctx;
|
|
||||||
|
|
||||||
handle.len = 32 * 2;
|
|
||||||
// Generate random nonce
|
// Generate random nonce
|
||||||
furi_hal_random_fill_buf(handle.nonce, 32);
|
furi_hal_random_fill_buf(handle.nonce, 32);
|
||||||
|
|
||||||
// Generate private key
|
{
|
||||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
mbedtls_md_context_t hmac_ctx;
|
||||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
mbedtls_md_init(&hmac_ctx);
|
||||||
hmac_sha256_update(&hmac_ctx, handle.nonce, 32);
|
MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1));
|
||||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, private);
|
MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key)));
|
||||||
|
|
||||||
// Generate private key handle
|
// Generate private key
|
||||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||||
hmac_sha256_update(&hmac_ctx, private, 32);
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, handle.nonce, sizeof(handle.nonce)));
|
||||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, private));
|
||||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, handle.hash);
|
|
||||||
|
MCHECK(mbedtls_md_hmac_reset(&hmac_ctx));
|
||||||
|
|
||||||
|
// Generate private key handle
|
||||||
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private)));
|
||||||
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||||
|
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash));
|
||||||
|
}
|
||||||
|
|
||||||
// Generate public key
|
// Generate public key
|
||||||
pub_key.format = 0x04; // Uncompressed point
|
u2f_ecc_compute_public_key(&U2F->group, private, &pub_key);
|
||||||
uECC_compute_public_key(private, pub_key.xy, U2F->p_curve);
|
|
||||||
|
|
||||||
// Generate signature
|
// Generate signature
|
||||||
uint8_t reserved_byte = 0;
|
{
|
||||||
sha256_start(&sha_ctx);
|
uint8_t reserved_byte = 0;
|
||||||
sha256_update(&sha_ctx, &reserved_byte, 1);
|
|
||||||
sha256_update(&sha_ctx, req->app_id, 32);
|
|
||||||
sha256_update(&sha_ctx, req->challenge, 32);
|
|
||||||
sha256_update(&sha_ctx, handle.hash, handle.len);
|
|
||||||
sha256_update(&sha_ctx, (uint8_t*)&pub_key, 65);
|
|
||||||
sha256_finish(&sha_ctx, hash);
|
|
||||||
|
|
||||||
uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve);
|
mbedtls_sha256_context sha_ctx;
|
||||||
|
|
||||||
|
mbedtls_sha256_init(&sha_ctx);
|
||||||
|
mbedtls_sha256_starts(&sha_ctx, 0);
|
||||||
|
|
||||||
|
mbedtls_sha256_update(&sha_ctx, &reserved_byte, 1);
|
||||||
|
mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id));
|
||||||
|
mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge));
|
||||||
|
mbedtls_sha256_update(&sha_ctx, handle.hash, handle.len);
|
||||||
|
mbedtls_sha256_update(&sha_ctx, (uint8_t*)&pub_key, sizeof(U2fPubKey));
|
||||||
|
|
||||||
|
mbedtls_sha256_finish(&sha_ctx, hash);
|
||||||
|
mbedtls_sha256_free(&sha_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign hash
|
||||||
|
u2f_ecc_sign(&U2F->group, U2F->cert_key, hash, signature);
|
||||||
|
|
||||||
// Encode response message
|
// Encode response message
|
||||||
resp->reserved = 0x05;
|
resp->reserved = 0x05;
|
||||||
@@ -250,13 +322,11 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
|||||||
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||||
U2fAuthReq* req = (U2fAuthReq*)buf;
|
U2fAuthReq* req = (U2fAuthReq*)buf;
|
||||||
U2fAuthResp* resp = (U2fAuthResp*)buf;
|
U2fAuthResp* resp = (U2fAuthResp*)buf;
|
||||||
uint8_t priv_key[32];
|
uint8_t priv_key[U2F_EC_KEY_SIZE];
|
||||||
uint8_t mac_control[32];
|
uint8_t mac_control[32];
|
||||||
hmac_sha256_context hmac_ctx;
|
|
||||||
sha256_context sha_ctx;
|
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
uint8_t hash[32];
|
uint8_t hash[U2F_HASH_SIZE];
|
||||||
uint8_t signature[64];
|
uint8_t signature[U2F_HASH_SIZE * 2];
|
||||||
uint32_t be_u2f_counter;
|
uint32_t be_u2f_counter;
|
||||||
|
|
||||||
if(u2f_data_check(false) == false) {
|
if(u2f_data_check(false) == false) {
|
||||||
@@ -281,26 +351,42 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
|||||||
be_u2f_counter = lfs_tobe32(U2F->counter + 1);
|
be_u2f_counter = lfs_tobe32(U2F->counter + 1);
|
||||||
|
|
||||||
// Generate hash
|
// Generate hash
|
||||||
sha256_start(&sha_ctx);
|
{
|
||||||
sha256_update(&sha_ctx, req->app_id, 32);
|
mbedtls_sha256_context sha_ctx;
|
||||||
sha256_update(&sha_ctx, &flags, 1);
|
|
||||||
sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), 4);
|
|
||||||
sha256_update(&sha_ctx, req->challenge, 32);
|
|
||||||
sha256_finish(&sha_ctx, hash);
|
|
||||||
|
|
||||||
// Recover private key
|
mbedtls_sha256_init(&sha_ctx);
|
||||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
mbedtls_sha256_starts(&sha_ctx, 0);
|
||||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
|
||||||
hmac_sha256_update(&hmac_ctx, req->key_handle.nonce, 32);
|
|
||||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, priv_key);
|
|
||||||
|
|
||||||
// Generate and verify private key handle
|
mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id));
|
||||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
mbedtls_sha256_update(&sha_ctx, &flags, 1);
|
||||||
hmac_sha256_update(&hmac_ctx, priv_key, 32);
|
mbedtls_sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), sizeof(be_u2f_counter));
|
||||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge));
|
||||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, mac_control);
|
|
||||||
|
|
||||||
if(memcmp(req->key_handle.hash, mac_control, 32) != 0) {
|
mbedtls_sha256_finish(&sha_ctx, hash);
|
||||||
|
mbedtls_sha256_free(&sha_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
mbedtls_md_context_t hmac_ctx;
|
||||||
|
mbedtls_md_init(&hmac_ctx);
|
||||||
|
MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1));
|
||||||
|
MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key)));
|
||||||
|
|
||||||
|
// Recover private key
|
||||||
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||||
|
MCHECK(mbedtls_md_hmac_update(
|
||||||
|
&hmac_ctx, req->key_handle.nonce, sizeof(req->key_handle.nonce)));
|
||||||
|
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, priv_key));
|
||||||
|
|
||||||
|
MCHECK(mbedtls_md_hmac_reset(&hmac_ctx));
|
||||||
|
|
||||||
|
// Generate and verify private key handle
|
||||||
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key)));
|
||||||
|
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||||
|
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) {
|
||||||
FURI_LOG_W(TAG, "Wrong handle!");
|
FURI_LOG_W(TAG, "Wrong handle!");
|
||||||
memcpy(&buf[0], state_wrong_data, 2);
|
memcpy(&buf[0], state_wrong_data, 2);
|
||||||
return 2;
|
return 2;
|
||||||
@@ -311,7 +397,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
uECC_sign(priv_key, hash, 32, signature, U2F->p_curve);
|
// Sign hash
|
||||||
|
u2f_ecc_sign(&U2F->group, priv_key, hash, signature);
|
||||||
|
|
||||||
resp->user_present = flags;
|
resp->user_present = flags;
|
||||||
resp->counter = be_u2f_counter;
|
resp->counter = be_u2f_counter;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ typedef struct {
|
|||||||
uint32_t counter;
|
uint32_t counter;
|
||||||
uint8_t random_salt[24];
|
uint8_t random_salt[24];
|
||||||
uint32_t control;
|
uint32_t control;
|
||||||
} __attribute__((packed)) U2fCounterData;
|
} FURI_PACKED U2fCounterData;
|
||||||
|
|
||||||
bool u2f_data_check(bool cert_only) {
|
bool u2f_data_check(bool cert_only) {
|
||||||
bool state = false;
|
bool state = false;
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str) {
|
|||||||
return u8g2_GetStrWidth(&canvas->fb, str);
|
return u8g2_GetStrWidth(&canvas->fb, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
|
uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol) {
|
||||||
furi_assert(canvas);
|
furi_assert(canvas);
|
||||||
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
|
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str);
|
|||||||
*
|
*
|
||||||
* @return width in pixels
|
* @return width in pixels
|
||||||
*/
|
*/
|
||||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol);
|
uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol);
|
||||||
|
|
||||||
/** Draw bitmap picture at position defined by x,y.
|
/** Draw bitmap picture at position defined by x,y.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -499,6 +499,23 @@ VariableItem* variable_item_list_add(
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position) {
|
||||||
|
VariableItem* item = NULL;
|
||||||
|
furi_assert(variable_item_list);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
variable_item_list->view,
|
||||||
|
VariableItemListModel * model,
|
||||||
|
{
|
||||||
|
if(position < VariableItemArray_size(model->items)) {
|
||||||
|
item = VariableItemArray_get(model->items, position);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
void variable_item_list_set_enter_callback(
|
void variable_item_list_set_enter_callback(
|
||||||
VariableItemList* variable_item_list,
|
VariableItemList* variable_item_list,
|
||||||
VariableItemListEnterCallback callback,
|
VariableItemListEnterCallback callback,
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ VariableItem* variable_item_list_add(
|
|||||||
VariableItemChangeCallback change_callback,
|
VariableItemChangeCallback change_callback,
|
||||||
void* context);
|
void* context);
|
||||||
|
|
||||||
|
/** Get item in VariableItemList
|
||||||
|
*
|
||||||
|
* @param variable_item_list VariableItemList instance
|
||||||
|
* @param position index of the item to get
|
||||||
|
*
|
||||||
|
* @return VariableItem* item instance
|
||||||
|
*/
|
||||||
|
VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position);
|
||||||
|
|
||||||
/** Set enter callback
|
/** Set enter callback
|
||||||
*
|
*
|
||||||
* @param variable_item_list VariableItemList instance
|
* @param variable_item_list VariableItemList instance
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ static bool notification_load_settings(NotificationApp* app) {
|
|||||||
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||||
|
|
||||||
if(fs_result) {
|
if(fs_result) {
|
||||||
uint16_t bytes_count = storage_file_read(file, &settings, settings_size);
|
size_t bytes_count = storage_file_read(file, &settings, settings_size);
|
||||||
|
|
||||||
if(bytes_count != settings_size) {
|
if(bytes_count != settings_size) {
|
||||||
fs_result = false;
|
fs_result = false;
|
||||||
@@ -488,7 +488,7 @@ static bool notification_save_settings(NotificationApp* app) {
|
|||||||
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||||
|
|
||||||
if(fs_result) {
|
if(fs_result) {
|
||||||
uint16_t bytes_count = storage_file_write(file, &settings, settings_size);
|
size_t bytes_count = storage_file_write(file, &settings, settings_size);
|
||||||
|
|
||||||
if(bytes_count != settings_size) {
|
if(bytes_count != settings_size) {
|
||||||
fs_result = false;
|
fs_result = false;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#include "flipper.pb.h"
|
|
||||||
#include "rpc_i.h"
|
#include "rpc_i.h"
|
||||||
#include "gui.pb.h"
|
|
||||||
#include <gui/gui_i.h>
|
#include <gui/gui_i.h>
|
||||||
#include <assets_icons.h>
|
#include <assets_icons.h>
|
||||||
|
|
||||||
|
#include <flipper.pb.h>
|
||||||
|
#include <gui.pb.h>
|
||||||
|
|
||||||
#define TAG "RpcGui"
|
#define TAG "RpcGui"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "rpc.h"
|
#include "rpc.h"
|
||||||
#include "storage/filesystem_api_defines.h"
|
#include <storage/filesystem_api_defines.h>
|
||||||
#include <pb.h>
|
#include <pb.h>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
#include "flipper.pb.h"
|
|
||||||
#include <core/common_defines.h>
|
#include <core/common_defines.h>
|
||||||
#include <core/memmgr.h>
|
#include <core/memmgr.h>
|
||||||
#include <core/record.h>
|
#include <core/record.h>
|
||||||
#include "pb_decode.h"
|
#include <rpc/rpc.h>
|
||||||
#include "rpc/rpc.h"
|
#include <rpc/rpc_i.h>
|
||||||
#include "rpc_i.h"
|
#include <storage/filesystem_api_defines.h>
|
||||||
#include "storage.pb.h"
|
#include <storage/storage.h>
|
||||||
#include "storage/filesystem_api_defines.h"
|
|
||||||
#include "storage/storage.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <lib/toolbox/md5_calc.h>
|
#include <lib/toolbox/md5_calc.h>
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
#include <update_util/lfs_backup.h>
|
#include <update_util/lfs_backup.h>
|
||||||
|
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <storage.pb.h>
|
||||||
|
#include <flipper.pb.h>
|
||||||
|
|
||||||
#define TAG "RpcStorage"
|
#define TAG "RpcStorage"
|
||||||
|
|
||||||
#define MAX_NAME_LENGTH 255
|
#define MAX_NAME_LENGTH 255
|
||||||
@@ -466,7 +466,7 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
|
|||||||
request->content.storage_write_request.file.data->size) {
|
request->content.storage_write_request.file.data->size) {
|
||||||
uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
|
uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
|
||||||
size_t buffer_size = request->content.storage_write_request.file.data->size;
|
size_t buffer_size = request->content.storage_write_request.file.data->size;
|
||||||
uint16_t written_size = storage_file_write(file, buffer, buffer_size);
|
size_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||||
fs_operation_success = (written_size == buffer_size);
|
fs_operation_success = (written_size == buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,13 @@ typedef struct {
|
|||||||
* @param total_space pointer to total space value
|
* @param total_space pointer to total space value
|
||||||
* @param free_space pointer to free space value
|
* @param free_space pointer to free space value
|
||||||
* @return FS_Error error info
|
* @return FS_Error error info
|
||||||
|
*
|
||||||
|
* @var FS_Common_Api::equivalent_path
|
||||||
|
* @brief Test whether two paths are equivalent (e.g differing case on a case-insensitive fs)
|
||||||
|
* @param path1 first path to be compared
|
||||||
|
* @param path2 second path to be compared
|
||||||
|
* @param truncate if set to true, compare only up to the path1's length
|
||||||
|
* @return true if path1 and path2 are considered equivalent
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo);
|
FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo);
|
||||||
@@ -175,6 +182,7 @@ typedef struct {
|
|||||||
const char* fs_path,
|
const char* fs_path,
|
||||||
uint64_t* total_space,
|
uint64_t* total_space,
|
||||||
uint64_t* free_space);
|
uint64_t* free_space);
|
||||||
|
bool (*const equivalent_path)(const char* path1, const char* path2);
|
||||||
} FS_Common_Api;
|
} FS_Common_Api;
|
||||||
|
|
||||||
/** Full filesystem api structure */
|
/** Full filesystem api structure */
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @file storage.h
|
||||||
|
* @brief APIs for working with storages, directories and files.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "filesystem_api_defines.h"
|
#include "filesystem_api_defines.h"
|
||||||
#include "storage_sd_api.h"
|
#include "storage_sd_api.h"
|
||||||
@@ -23,43 +28,62 @@ extern "C" {
|
|||||||
|
|
||||||
typedef struct Storage Storage;
|
typedef struct Storage Storage;
|
||||||
|
|
||||||
/** Allocates and initializes a file descriptor
|
/**
|
||||||
* @return File*
|
* @brief Allocate and initialize a file instance.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return pointer to the created instance.
|
||||||
*/
|
*/
|
||||||
File* storage_file_alloc(Storage* storage);
|
File* storage_file_alloc(Storage* storage);
|
||||||
|
|
||||||
/** Frees the file descriptor. Closes the file if it was open.
|
/**
|
||||||
|
* @brief Free the file instance.
|
||||||
|
*
|
||||||
|
* If the file was open, calling this function will close it automatically.
|
||||||
|
* @param file pointer to the file instance to be freed.
|
||||||
*/
|
*/
|
||||||
void storage_file_free(File* file);
|
void storage_file_free(File* file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration of events emitted by the storage through the PubSub system.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
StorageEventTypeCardMount,
|
StorageEventTypeCardMount, /**< SD card was mounted. */
|
||||||
StorageEventTypeCardUnmount,
|
StorageEventTypeCardUnmount, /**< SD card was unmounted. */
|
||||||
StorageEventTypeCardMountError,
|
StorageEventTypeCardMountError, /**< An error occurred during mounting of an SD card. */
|
||||||
StorageEventTypeFileClose,
|
StorageEventTypeFileClose, /**< A file was closed. */
|
||||||
StorageEventTypeDirClose,
|
StorageEventTypeDirClose, /**< A directory was closed. */
|
||||||
} StorageEventType;
|
} StorageEventType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Storage event (passed to the PubSub callback).
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
StorageEventType type;
|
StorageEventType type; /**< Type of the event. */
|
||||||
} StorageEvent;
|
} StorageEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get storage pubsub.
|
* @brief Get the storage pubsub instance.
|
||||||
|
*
|
||||||
* Storage will send StorageEvent messages.
|
* Storage will send StorageEvent messages.
|
||||||
* @param storage
|
*
|
||||||
* @return FuriPubSub*
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return pointer to the pubsub instance.
|
||||||
*/
|
*/
|
||||||
FuriPubSub* storage_get_pubsub(Storage* storage);
|
FuriPubSub* storage_get_pubsub(Storage* storage);
|
||||||
|
|
||||||
/******************* File Functions *******************/
|
/******************* File Functions *******************/
|
||||||
|
|
||||||
/** Opens an existing file or create a new one.
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Open an existing file or create a new one.
|
||||||
* @param path path to file
|
*
|
||||||
* @param access_mode access mode from FS_AccessMode
|
* @warning The calling code MUST call storage_file_close() even if the open operation had failed.
|
||||||
|
*
|
||||||
|
* @param file pointer to the file instance to be opened.
|
||||||
|
* @param path pointer to a zero-terminated string containing the path to the file to be opened.
|
||||||
|
* @param access_mode access mode from FS_AccessMode.
|
||||||
* @param open_mode open mode from FS_OpenMode
|
* @param open_mode open mode from FS_OpenMode
|
||||||
* @return success flag. You need to close the file even if the open operation failed.
|
* @return true if the file was successfully opened, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_open(
|
bool storage_file_open(
|
||||||
File* file,
|
File* file,
|
||||||
@@ -67,202 +91,267 @@ bool storage_file_open(
|
|||||||
FS_AccessMode access_mode,
|
FS_AccessMode access_mode,
|
||||||
FS_OpenMode open_mode);
|
FS_OpenMode open_mode);
|
||||||
|
|
||||||
/** Close the file.
|
/**
|
||||||
* @param file pointer to a file object, the file object will be freed.
|
* @brief Close the file.
|
||||||
* @return success flag
|
*
|
||||||
|
* @param file pointer to the file instance to be closed.
|
||||||
|
* @return true if the file was successfully closed, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_close(File* file);
|
bool storage_file_close(File* file);
|
||||||
|
|
||||||
/** Tells if the file is open
|
/**
|
||||||
* @param file pointer to a file object
|
* @brief Check whether the file is open.
|
||||||
* @return bool true if file is open
|
*
|
||||||
|
* @param file pointer to the file instance in question.
|
||||||
|
* @return true if the file is open, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_is_open(File* file);
|
bool storage_file_is_open(File* file);
|
||||||
|
|
||||||
/** Tells if the file is a directory
|
/**
|
||||||
* @param file pointer to a file object
|
* @brief Check whether a file instance represents a directory.
|
||||||
* @return bool true if file is a directory
|
*
|
||||||
|
* @param file pointer to the file instance in question.
|
||||||
|
* @return true if the file instance represents a directory, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_is_dir(File* file);
|
bool storage_file_is_dir(File* file);
|
||||||
|
|
||||||
/** Reads bytes from a file into a buffer
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Read bytes from a file into a buffer.
|
||||||
* @param buff pointer to a buffer, for reading
|
*
|
||||||
* @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer.
|
* @param file pointer to the file instance to read from.
|
||||||
* @return uint16_t how many bytes were actually read
|
* @param buff pointer to the buffer to be filled with read data.
|
||||||
|
* @param bytes_to_read number of bytes to read. Must be less than or equal to the size of the buffer.
|
||||||
|
* @return actual number of bytes read (may be fewer than requested).
|
||||||
*/
|
*/
|
||||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read);
|
size_t storage_file_read(File* file, void* buff, size_t bytes_to_read);
|
||||||
|
|
||||||
/** Writes bytes from a buffer to a file
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Write bytes from a buffer to a file.
|
||||||
* @param buff pointer to buffer, for writing
|
*
|
||||||
* @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer.
|
* @param file pointer to the file instance to write into.
|
||||||
* @return uint16_t how many bytes were actually written
|
* @param buff pointer to the buffer containing the data to be written.
|
||||||
|
* @param bytes_to_write number of bytes to write. Must be less than or equal to the size of the buffer.
|
||||||
|
* @return actual number of bytes written (may be fewer than requested).
|
||||||
*/
|
*/
|
||||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write);
|
size_t storage_file_write(File* file, const void* buff, size_t bytes_to_write);
|
||||||
|
|
||||||
/** Moves the r/w pointer
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Change the current access position in a file.
|
||||||
* @param offset offset to move the r/w pointer
|
*
|
||||||
* @param from_start set an offset from the start or from the current position
|
* @param file pointer to the file instance in question.
|
||||||
|
* @param offset access position offset (meaning depends on from_start parameter).
|
||||||
|
* @param from_start if true, set the access position relative to the file start, otherwise relative to the current position.
|
||||||
* @return success flag
|
* @return success flag
|
||||||
*/
|
*/
|
||||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start);
|
bool storage_file_seek(File* file, uint32_t offset, bool from_start);
|
||||||
|
|
||||||
/** Gets the position of the r/w pointer
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Get the current access position.
|
||||||
* @return uint64_t position of the r/w pointer
|
*
|
||||||
|
* @param file pointer to the file instance in question.
|
||||||
|
* @return current access position.
|
||||||
*/
|
*/
|
||||||
uint64_t storage_file_tell(File* file);
|
uint64_t storage_file_tell(File* file);
|
||||||
|
|
||||||
/** Truncates the file size to the current position of the r/w pointer
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Truncate the file size to the current access position.
|
||||||
* @return bool success flag
|
*
|
||||||
|
* @param file pointer to the file instance to be truncated.
|
||||||
|
* @return true if the file was successfully truncated, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_truncate(File* file);
|
bool storage_file_truncate(File* file);
|
||||||
|
|
||||||
/** Gets the size of the file
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Get the file size.
|
||||||
* @return uint64_t size of the file
|
*
|
||||||
|
* @param file pointer to the file instance in question.
|
||||||
|
* @return size of the file, in bytes.
|
||||||
*/
|
*/
|
||||||
uint64_t storage_file_size(File* file);
|
uint64_t storage_file_size(File* file);
|
||||||
|
|
||||||
/** Writes file cache to storage
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Synchronise the file cache with the actual storage.
|
||||||
* @return bool success flag
|
*
|
||||||
|
* @param file pointer to the file instance in question.
|
||||||
|
* @return true if the file was successfully synchronised, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_sync(File* file);
|
bool storage_file_sync(File* file);
|
||||||
|
|
||||||
/** Checks that the r/w pointer is at the end of the file
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Check whether the current access position is at the end of the file.
|
||||||
* @return bool success flag
|
*
|
||||||
|
* @param file pointer to a file instance in question.
|
||||||
|
* @return bool true if the current access position is at the end of the file, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_eof(File* file);
|
bool storage_file_eof(File* file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that file exists
|
* @brief Check whether a file exists.
|
||||||
*
|
*
|
||||||
* @param storage
|
* @param storage pointer to a storage API instance.
|
||||||
* @param path
|
* @param path pointer to a zero-terminated string containing the path to the file in question.
|
||||||
* @return true if file exists
|
* @return true if the file exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_exists(Storage* storage, const char* path);
|
bool storage_file_exists(Storage* storage, const char* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copy data from one opened file to another opened file
|
* @brief Copy data from a source file to the destination file.
|
||||||
* Size bytes will be copied from current position of source file to current position of destination file
|
|
||||||
*
|
*
|
||||||
* @param source source file
|
* Both files must be opened prior to calling this function.
|
||||||
* @param destination destination file
|
*
|
||||||
* @param size size of data to copy
|
* The requested amount of bytes will be copied from the current access position
|
||||||
* @return bool success flag
|
* in the source file to the current access position in the destination file.
|
||||||
|
*
|
||||||
|
* @param source pointer to a source file instance.
|
||||||
|
* @param destination pointer to a destination file instance.
|
||||||
|
* @param size data size to be copied, in bytes.
|
||||||
|
* @return true if the data was successfully copied, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_file_copy_to_file(File* source, File* destination, uint32_t size);
|
bool storage_file_copy_to_file(File* source, File* destination, size_t size);
|
||||||
|
|
||||||
/******************* Dir Functions *******************/
|
/******************* Directory Functions *******************/
|
||||||
|
|
||||||
/** Opens a directory to get objects from it
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Open a directory.
|
||||||
* @param file pointer to file object.
|
*
|
||||||
* @param path path to directory
|
* Opening a directory is necessary to be able to read its contents with storage_dir_read().
|
||||||
* @return bool success flag. You need to close the directory even if the open operation failed.
|
*
|
||||||
|
* @warning The calling code MUST call storage_dir_close() even if the open operation had failed.
|
||||||
|
*
|
||||||
|
* @param file pointer to a file instance representing the directory in question.
|
||||||
|
* @param path pointer to a zero-terminated string containing the path of the directory in question.
|
||||||
|
* @return true if the directory was successfully opened, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_dir_open(File* file, const char* path);
|
bool storage_dir_open(File* file, const char* path);
|
||||||
|
|
||||||
/** Close the directory. Also free file handle structure and point it to the NULL.
|
/**
|
||||||
* @param file pointer to a file object.
|
* @brief Close the directory.
|
||||||
* @return bool success flag
|
*
|
||||||
|
* @param file pointer to a file instance representing the directory in question.
|
||||||
|
* @return true if the directory was successfully closed, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_dir_close(File* file);
|
bool storage_dir_close(File* file);
|
||||||
|
|
||||||
/** Reads the next object in the directory
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Get the next item in the directory.
|
||||||
* @param fileinfo pointer to the read FileInfo, may be NULL
|
*
|
||||||
* @param name pointer to name buffer, may be NULL
|
* If the next object does not exist, this function returns false as well
|
||||||
* @param name_length name buffer length
|
* and sets the file error id to FSE_NOT_EXIST.
|
||||||
* @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST)
|
*
|
||||||
|
* @param file pointer to a file instance representing the directory in question.
|
||||||
|
* @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL).
|
||||||
|
* @param name pointer to the buffer to contain the name (may be NULL).
|
||||||
|
* @param name_length maximum capacity of the name buffer, in bytes.
|
||||||
|
* @return true if the next item was successfully read, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||||
|
|
||||||
/** Rewinds the read pointer to first item in the directory
|
/**
|
||||||
* @param file pointer to file object.
|
* @brief Change the access position to first item in the directory.
|
||||||
* @return bool success flag
|
*
|
||||||
|
* @param file pointer to a file instance representing the directory in question.
|
||||||
|
* @return true if the access position was successfully changed, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_dir_rewind(File* file);
|
bool storage_dir_rewind(File* file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that dir exists
|
* @brief Check whether a directory exists.
|
||||||
*
|
*
|
||||||
* @param storage
|
* @param storage pointer to a storage API instance.
|
||||||
* @param path
|
* @param path pointer to a zero-terminated string containing the path of the directory in question.
|
||||||
* @return bool
|
* @return true if the directory exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_dir_exists(Storage* storage, const char* path);
|
bool storage_dir_exists(Storage* storage, const char* path);
|
||||||
|
|
||||||
/******************* Common Functions *******************/
|
/******************* Common Functions *******************/
|
||||||
|
|
||||||
/** Retrieves unix timestamp of last access
|
/**
|
||||||
|
* @brief Get the last access time in UNIX format.
|
||||||
*
|
*
|
||||||
* @param storage The storage instance
|
* @param storage pointer to a storage API instance.
|
||||||
* @param path path to file/directory
|
* @param path pointer to a zero-terminated string containing the path of the item in question.
|
||||||
* @param timestamp the timestamp pointer
|
* @param timestamp pointer to a value to contain the timestamp.
|
||||||
*
|
* @return FSE_OK if the timestamp has been successfully received, any other error code on failure.
|
||||||
* @return FS_Error operation result
|
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp);
|
FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp);
|
||||||
|
|
||||||
/** Retrieves information about a file/directory
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Get information about a file or a directory.
|
||||||
* @param path path to file/directory
|
*
|
||||||
* @param fileinfo pointer to the read FileInfo, may be NULL
|
* @param storage pointer to a storage API instance.
|
||||||
* @return FS_Error operation result
|
* @param path pointer to a zero-terminated string containing the path of the item in question.
|
||||||
|
* @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL).
|
||||||
|
* @return FSE_OK if the info has been successfully received, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo);
|
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo);
|
||||||
|
|
||||||
/** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Remove a file or a directory.
|
||||||
* @param path
|
*
|
||||||
* @return FS_Error operation result
|
* The directory must be empty.
|
||||||
|
* The file or the directory must NOT be open.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param path pointer to a zero-terminated string containing the path of the item to be removed.
|
||||||
|
* @return FSE_OK if the file or directory has been successfully removed, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_remove(Storage* storage, const char* path);
|
FS_Error storage_common_remove(Storage* storage, const char* path);
|
||||||
|
|
||||||
/** Renames file/directory, file/directory must not be open. Will overwrite existing file.
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Rename a file or a directory.
|
||||||
* @param old_path old path
|
*
|
||||||
* @param new_path new path
|
* The file or the directory must NOT be open.
|
||||||
* @return FS_Error operation result
|
* Will overwrite the destination file if it already exists.
|
||||||
|
*
|
||||||
|
* Renaming a regular file to itself does nothing and always succeeds.
|
||||||
|
* Renaming a directory to itself or to a subdirectory of itself always fails.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||||
|
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||||
|
* @return FSE_OK if the file or directory has been successfully renamed, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path);
|
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path);
|
||||||
|
|
||||||
/** Copy file, file must not be open
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Copy the file to a new location.
|
||||||
* @param old_path old path
|
*
|
||||||
* @param new_path new path
|
* The file must NOT be open at the time of calling this function.
|
||||||
* @return FS_Error operation result
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||||
|
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||||
|
* @return FSE_OK if the file has been successfully copied, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path);
|
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path);
|
||||||
|
|
||||||
/** Copy one folder contents into another with rename of all conflicting files
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Copy the contents of one directory into another and rename all conflicting files.
|
||||||
* @param old_path old path
|
*
|
||||||
* @param new_path new path
|
* @param storage pointer to a storage API instance.
|
||||||
* @return FS_Error operation result
|
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||||
|
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||||
|
* @return FSE_OK if the directories have been successfully merged, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path);
|
FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path);
|
||||||
|
|
||||||
/** Creates a directory
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Create a directory.
|
||||||
* @param path directory path
|
*
|
||||||
* @return FS_Error operation result
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param fs_path pointer to a zero-terminated string containing the directory path.
|
||||||
|
* @return FSE_OK if the directory has been successfully created, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_mkdir(Storage* storage, const char* path);
|
FS_Error storage_common_mkdir(Storage* storage, const char* path);
|
||||||
|
|
||||||
/** Gets general information about the storage
|
/**
|
||||||
* @param app pointer to the api
|
* @brief Get the general information about the storage.
|
||||||
* @param fs_path the path to the storage of interest
|
*
|
||||||
* @param total_space pointer to total space record, will be filled
|
* @param storage pointer to a storage API instance.
|
||||||
* @param free_space pointer to free space record, will be filled
|
* @param fs_path pointer to a zero-terminated string containing the path to the storage question.
|
||||||
* @return FS_Error operation result
|
* @param total_space pointer to the value to contain the total capacity, in bytes.
|
||||||
|
* @param free_space pointer to the value to contain the available space, in bytes.
|
||||||
|
* @return FSE_OK if the information has been successfully received, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_fs_info(
|
FS_Error storage_common_fs_info(
|
||||||
Storage* storage,
|
Storage* storage,
|
||||||
@@ -271,150 +360,242 @@ FS_Error storage_common_fs_info(
|
|||||||
uint64_t* free_space);
|
uint64_t* free_space);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse aliases in path and replace them with real path
|
* @brief Parse aliases in a path and replace them with the real path.
|
||||||
* Also will create special folders if they are not exist
|
|
||||||
*
|
*
|
||||||
* @param storage
|
* Necessary special directories will be created automatically if they did not exist.
|
||||||
* @param path
|
*
|
||||||
* @return bool
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param path pointer to a zero-terminated string containing the path in question.
|
||||||
|
* @return true if the path was successfully resolved, false otherwise.
|
||||||
*/
|
*/
|
||||||
void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path);
|
void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Move content of one folder to another, with rename of all conflicting files.
|
* @brief Move the contents of source folder to destination one and rename all conflicting files.
|
||||||
* Source folder will be deleted if the migration is successful.
|
|
||||||
*
|
*
|
||||||
* @param storage
|
* Source folder will be deleted if the migration was successful.
|
||||||
* @param source
|
*
|
||||||
* @param dest
|
* @param storage pointer to a storage API instance.
|
||||||
* @return FS_Error
|
* @param source pointer to a zero-terminated string containing the source path.
|
||||||
|
* @param dest pointer to a zero-terminated string containing the destination path.
|
||||||
|
* @return FSE_OK if the migration was successfull completed, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest);
|
FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that file or dir exists
|
* @brief Check whether a file or a directory exists.
|
||||||
*
|
*
|
||||||
* @param storage
|
* @param storage pointer to a storage API instance.
|
||||||
* @param path
|
* @param path pointer to a zero-terminated string containing the path in question.
|
||||||
* @return bool
|
* @return true if a file or a directory exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_common_exists(Storage* storage, const char* path);
|
bool storage_common_exists(Storage* storage, const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether two paths are equivalent.
|
||||||
|
*
|
||||||
|
* This function will resolve aliases and apply filesystem-specific
|
||||||
|
* rules to determine whether the two given paths are equivalent.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - /int/text and /ext/test -> false (Different storages),
|
||||||
|
* - /int/Test and /int/test -> false (Case-sensitive storage),
|
||||||
|
* - /ext/Test and /ext/test -> true (Case-insensitive storage).
|
||||||
|
*
|
||||||
|
* If the truncate parameter is set to true, the second path will be
|
||||||
|
* truncated to be no longer than the first one. It is useful to determine
|
||||||
|
* whether path2 is a subdirectory of path1.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param path1 pointer to a zero-terminated string containing the first path.
|
||||||
|
* @param path2 pointer to a zero-terminated string containing the second path.
|
||||||
|
* @param truncate whether to truncate path2 to be no longer than path1.
|
||||||
|
* @return true if paths are equivalent, false otherwise.
|
||||||
|
*/
|
||||||
|
bool storage_common_equivalent_path(
|
||||||
|
Storage* storage,
|
||||||
|
const char* path1,
|
||||||
|
const char* path2,
|
||||||
|
bool truncate);
|
||||||
|
|
||||||
/******************* Error Functions *******************/
|
/******************* Error Functions *******************/
|
||||||
|
|
||||||
/** Retrieves the error text from the error id
|
/**
|
||||||
* @param error_id error id
|
* @brief Get the textual description of a numeric error identifer.
|
||||||
* @return const char* error text
|
*
|
||||||
|
* @param error_id numeric identifier of the error in question.
|
||||||
|
* @return pointer to a statically allocated zero-terminated string containing the respective error text.
|
||||||
*/
|
*/
|
||||||
const char* storage_error_get_desc(FS_Error error_id);
|
const char* storage_error_get_desc(FS_Error error_id);
|
||||||
|
|
||||||
/** Retrieves the error id from the file object
|
/**
|
||||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR ID IF THE FILE HAS BEEN CLOSED
|
* @brief Get the numeric error identifier from a file instance.
|
||||||
* @return FS_Error error id
|
*
|
||||||
|
* @warning It is not possible to get the error identifier after the file has been closed.
|
||||||
|
*
|
||||||
|
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||||
|
* @return numeric identifier of the last error associated with the file instance.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_file_get_error(File* file);
|
FS_Error storage_file_get_error(File* file);
|
||||||
|
|
||||||
/** Retrieves the internal (storage-specific) error id from the file object
|
/**
|
||||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED
|
* @brief Get the internal (storage-specific) numeric error identifier from a file instance.
|
||||||
* @return FS_Error error id
|
*
|
||||||
|
* @warning It is not possible to get the internal error identifier after the file has been closed.
|
||||||
|
*
|
||||||
|
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||||
|
* @return numeric identifier of the last internal error associated with the file instance.
|
||||||
*/
|
*/
|
||||||
int32_t storage_file_get_internal_error(File* file);
|
int32_t storage_file_get_internal_error(File* file);
|
||||||
|
|
||||||
/** Retrieves the error text from the file object
|
/**
|
||||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED
|
* @brief Get the textual description of a the last error associated with a file instance.
|
||||||
* @return const char* error text
|
*
|
||||||
|
* @warning It is not possible to get the error text after the file has been closed.
|
||||||
|
*
|
||||||
|
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||||
|
* @return pointer to a statically allocated zero-terminated string containing the respective error text.
|
||||||
*/
|
*/
|
||||||
const char* storage_file_get_error_desc(File* file);
|
const char* storage_file_get_error_desc(File* file);
|
||||||
|
|
||||||
/******************* SD Card Functions *******************/
|
/******************* SD Card Functions *******************/
|
||||||
|
|
||||||
/** Formats SD Card
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Format the SD Card.
|
||||||
* @return FS_Error operation result
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return FSE_OK if the card was successfully formatted, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_sd_format(Storage* api);
|
FS_Error storage_sd_format(Storage* storage);
|
||||||
|
|
||||||
/** Will unmount the SD card.
|
/**
|
||||||
* Will return FSE_NOT_READY if the SD card is not mounted.
|
* @brief Unmount the SD card.
|
||||||
* Will return FSE_DENIED if there are open files on the SD card.
|
*
|
||||||
* @param api pointer to the api
|
* These return values have special meaning:
|
||||||
* @return FS_Error operation result
|
* - FSE_NOT_READY if the SD card is not mounted.
|
||||||
|
* - FSE_DENIED if there are open files on the SD card.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return FSE_OK if the card was successfully formatted, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_sd_unmount(Storage* api);
|
FS_Error storage_sd_unmount(Storage* storage);
|
||||||
|
|
||||||
/** Will mount the SD card
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Mount the SD card.
|
||||||
* @return FS_Error operation result
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return FSE_OK if the card was successfully mounted, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_sd_mount(Storage* api);
|
FS_Error storage_sd_mount(Storage* storage);
|
||||||
|
|
||||||
/** Retrieves SD card information
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Get SD card information.
|
||||||
* @param info pointer to the info
|
*
|
||||||
* @return FS_Error operation result
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param info pointer to the info object to contain the requested information.
|
||||||
|
* @return FSE_OK if the info was successfully received, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_sd_info(Storage* api, SDInfo* info);
|
FS_Error storage_sd_info(Storage* storage, SDInfo* info);
|
||||||
|
|
||||||
/** Retrieves SD card status
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Get SD card status.
|
||||||
* @return FS_Error operation result
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @return storage status in the form of a numeric error identifier.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_sd_status(Storage* api);
|
FS_Error storage_sd_status(Storage* storage);
|
||||||
|
|
||||||
/******************* Internal LFS Functions *******************/
|
/******************* Internal LFS Functions *******************/
|
||||||
|
|
||||||
typedef void (*Storage_name_converter)(FuriString*);
|
typedef void (*Storage_name_converter)(FuriString*);
|
||||||
|
|
||||||
/** Backs up internal storage to a tar archive
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Back up the internal storage contents to a *.tar archive.
|
||||||
* @param dstname destination archive path
|
*
|
||||||
* @return FS_Error operation result
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param dstname pointer to a zero-terminated string containing the archive file path.
|
||||||
|
* @return FSE_OK if the storage was successfully backed up, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_int_backup(Storage* api, const char* dstname);
|
FS_Error storage_int_backup(Storage* storage, const char* dstname);
|
||||||
|
|
||||||
/** Restores internal storage from a tar archive
|
/**
|
||||||
* @param api pointer to the api
|
* @brief Restore the internal storage contents from a *.tar archive.
|
||||||
* @param dstname archive path
|
*
|
||||||
* @param converter pointer to filename conversion function, may be NULL
|
* @param storage pointer to a storage API instance.
|
||||||
* @return FS_Error operation result
|
* @param dstname pointer to a zero-terminated string containing the archive file path.
|
||||||
|
* @param converter pointer to a filename conversion function (may be NULL).
|
||||||
|
* @return FSE_OK if the storage was successfully restored, any other error code on failure.
|
||||||
*/
|
*/
|
||||||
FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter);
|
FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter);
|
||||||
|
|
||||||
/***************** Simplified Functions ******************/
|
/***************** Simplified Functions ******************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a file/directory, the directory must be empty and the file/directory must not be open
|
* @brief Remove a file or a directory.
|
||||||
* @param storage pointer to the api
|
*
|
||||||
* @param path
|
* The following conditions must be met:
|
||||||
* @return true on success or if file/dir is not exist
|
* - the directory must be empty.
|
||||||
|
* - the file or the directory must NOT be open.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param path pointer to a zero-terminated string containing the item path.
|
||||||
|
* @return true on success or if the item does not exist, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_simply_remove(Storage* storage, const char* path);
|
bool storage_simply_remove(Storage* storage, const char* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively removes a file/directory, the directory can be not empty
|
* @brief Recursively remove a file or a directory.
|
||||||
* @param storage pointer to the api
|
*
|
||||||
* @param path
|
* Unlike storage_simply_remove(), the directory does not need to be empty.
|
||||||
* @return true on success or if file/dir is not exist
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param path pointer to a zero-terminated string containing the item path.
|
||||||
|
* @return true on success or if the item does not exist, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_simply_remove_recursive(Storage* storage, const char* path);
|
bool storage_simply_remove_recursive(Storage* storage, const char* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a directory
|
* @brief Create a directory.
|
||||||
* @param storage
|
*
|
||||||
* @param path
|
* @param storage pointer to a storage API instance.
|
||||||
* @return true on success or if directory is already exist
|
* @param path pointer to a zero-terminated string containing the directory path.
|
||||||
|
* @return true on success or if directory does already exist, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool storage_simply_mkdir(Storage* storage, const char* path);
|
bool storage_simply_mkdir(Storage* storage, const char* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get next free filename.
|
* @brief Get the next free filename in a directory.
|
||||||
*
|
*
|
||||||
* @param storage
|
* Usage example:
|
||||||
* @param dirname
|
* ```c
|
||||||
* @param filename
|
* FuriString* file_name = furi_string_alloc();
|
||||||
* @param fileextension
|
* Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
* @param nextfilename return name
|
*
|
||||||
* @param max_len max len name
|
* storage_get_next_filename(storage,
|
||||||
|
* "/ext/test",
|
||||||
|
* "cookies",
|
||||||
|
* ".yum",
|
||||||
|
* 20);
|
||||||
|
*
|
||||||
|
* furi_record_close(RECORD_STORAGE);
|
||||||
|
*
|
||||||
|
* use_file_name(file_name);
|
||||||
|
*
|
||||||
|
* furi_string_free(file_name);
|
||||||
|
* ```
|
||||||
|
* Possible file_name values after calling storage_get_next_filename():
|
||||||
|
* "cookies", "cookies1", "cookies2", ... etc depending on whether any of
|
||||||
|
* these files have already existed in the directory.
|
||||||
|
*
|
||||||
|
* @note If the resulting next file name length is greater than set by the max_len
|
||||||
|
* parameter, the original filename will be returned instead.
|
||||||
|
*
|
||||||
|
* @param storage pointer to a storage API instance.
|
||||||
|
* @param dirname pointer to a zero-terminated string containing the directory path.
|
||||||
|
* @param filename pointer to a zero-terminated string containing the file name.
|
||||||
|
* @param fileextension pointer to a zero-terminated string containing the file extension.
|
||||||
|
* @param nextfilename pointer to a dynamic string containing the resulting file name.
|
||||||
|
* @param max_len maximum length of the new name.
|
||||||
*/
|
*/
|
||||||
void storage_get_next_filename(
|
void storage_get_next_filename(
|
||||||
Storage* storage,
|
Storage* storage,
|
||||||
|
|||||||
@@ -198,15 +198,15 @@ static void storage_cli_read(Cli* cli, FuriString* path) {
|
|||||||
File* file = storage_file_alloc(api);
|
File* file = storage_file_alloc(api);
|
||||||
|
|
||||||
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
const uint16_t buffer_size = 128;
|
const size_t buffer_size = 128;
|
||||||
uint16_t read_size = 0;
|
size_t read_size = 0;
|
||||||
uint8_t* data = malloc(buffer_size);
|
uint8_t* data = malloc(buffer_size);
|
||||||
|
|
||||||
printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
|
printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
|
||||||
|
|
||||||
do {
|
do {
|
||||||
read_size = storage_file_read(file, data, buffer_size);
|
read_size = storage_file_read(file, data, buffer_size);
|
||||||
for(uint16_t i = 0; i < read_size; i++) {
|
for(size_t i = 0; i < read_size; i++) {
|
||||||
printf("%c", data[i]);
|
printf("%c", data[i]);
|
||||||
}
|
}
|
||||||
} while(read_size > 0);
|
} while(read_size > 0);
|
||||||
@@ -227,7 +227,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
|||||||
Storage* api = furi_record_open(RECORD_STORAGE);
|
Storage* api = furi_record_open(RECORD_STORAGE);
|
||||||
File* file = storage_file_alloc(api);
|
File* file = storage_file_alloc(api);
|
||||||
|
|
||||||
const uint16_t buffer_size = 512;
|
const size_t buffer_size = 512;
|
||||||
uint8_t* buffer = malloc(buffer_size);
|
uint8_t* buffer = malloc(buffer_size);
|
||||||
|
|
||||||
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||||
@@ -239,10 +239,10 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
|||||||
uint8_t symbol = cli_getc(cli);
|
uint8_t symbol = cli_getc(cli);
|
||||||
|
|
||||||
if(symbol == CliSymbolAsciiETX) {
|
if(symbol == CliSymbolAsciiETX) {
|
||||||
uint16_t write_size = read_index % buffer_size;
|
size_t write_size = read_index % buffer_size;
|
||||||
|
|
||||||
if(write_size > 0) {
|
if(write_size > 0) {
|
||||||
uint16_t written_size = storage_file_write(file, buffer, write_size);
|
size_t written_size = storage_file_write(file, buffer, write_size);
|
||||||
|
|
||||||
if(written_size != write_size) {
|
if(written_size != write_size) {
|
||||||
storage_cli_print_error(storage_file_get_error(file));
|
storage_cli_print_error(storage_file_get_error(file));
|
||||||
@@ -257,7 +257,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
|||||||
read_index++;
|
read_index++;
|
||||||
|
|
||||||
if(((read_index % buffer_size) == 0)) {
|
if(((read_index % buffer_size) == 0)) {
|
||||||
uint16_t written_size = storage_file_write(file, buffer, buffer_size);
|
size_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||||
|
|
||||||
if(written_size != buffer_size) {
|
if(written_size != buffer_size) {
|
||||||
storage_cli_print_error(storage_file_get_error(file));
|
storage_cli_print_error(storage_file_get_error(file));
|
||||||
@@ -289,7 +289,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
|
|||||||
} else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
} else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
uint64_t file_size = storage_file_size(file);
|
uint64_t file_size = storage_file_size(file);
|
||||||
|
|
||||||
printf("Size: %lu\r\n", (uint32_t)file_size);
|
printf("Size: %llu\r\n", file_size);
|
||||||
|
|
||||||
if(buffer_size) {
|
if(buffer_size) {
|
||||||
uint8_t* data = malloc(buffer_size);
|
uint8_t* data = malloc(buffer_size);
|
||||||
@@ -297,8 +297,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
|
|||||||
printf("\r\nReady?\r\n");
|
printf("\r\nReady?\r\n");
|
||||||
cli_getc(cli);
|
cli_getc(cli);
|
||||||
|
|
||||||
uint16_t read_size = storage_file_read(file, data, buffer_size);
|
size_t read_size = storage_file_read(file, data, buffer_size);
|
||||||
for(uint16_t i = 0; i < read_size; i++) {
|
for(size_t i = 0; i < read_size; i++) {
|
||||||
putchar(data[i]);
|
putchar(data[i]);
|
||||||
}
|
}
|
||||||
file_size -= read_size;
|
file_size -= read_size;
|
||||||
@@ -335,7 +335,7 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args
|
|||||||
|
|
||||||
size_t read_bytes = cli_read(cli, buffer, buffer_size);
|
size_t read_bytes = cli_read(cli, buffer, buffer_size);
|
||||||
|
|
||||||
uint16_t written_size = storage_file_write(file, buffer, read_bytes);
|
size_t written_size = storage_file_write(file, buffer, read_bytes);
|
||||||
|
|
||||||
if(written_size != buffer_size) {
|
if(written_size != buffer_size) {
|
||||||
storage_cli_print_error(storage_file_get_error(file));
|
storage_cli_print_error(storage_file_get_error(file));
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ bool storage_file_close(File* file) {
|
|||||||
return S_RETURN_BOOL;
|
return S_RETURN_BOOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
|
static uint16_t storage_file_read_underlying(File* file, void* buff, uint16_t bytes_to_read) {
|
||||||
if(bytes_to_read == 0) {
|
if(bytes_to_read == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,8 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
|
|||||||
return S_RETURN_UINT16;
|
return S_RETURN_UINT16;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) {
|
static uint16_t
|
||||||
|
storage_file_write_underlying(File* file, const void* buff, uint16_t bytes_to_write) {
|
||||||
if(bytes_to_write == 0) {
|
if(bytes_to_write == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -179,6 +180,40 @@ uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_writ
|
|||||||
return S_RETURN_UINT16;
|
return S_RETURN_UINT16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t storage_file_read(File* file, void* buff, size_t to_read) {
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
const size_t max_chunk = UINT16_MAX;
|
||||||
|
do {
|
||||||
|
const size_t chunk = MIN((to_read - total), max_chunk);
|
||||||
|
size_t read = storage_file_read_underlying(file, buff + total, chunk);
|
||||||
|
total += read;
|
||||||
|
|
||||||
|
if(storage_file_get_error(file) != FSE_OK || read != chunk) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(total != to_read);
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t storage_file_write(File* file, const void* buff, size_t to_write) {
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
const size_t max_chunk = UINT16_MAX;
|
||||||
|
do {
|
||||||
|
const size_t chunk = MIN((to_write - total), max_chunk);
|
||||||
|
size_t written = storage_file_write_underlying(file, buff + total, chunk);
|
||||||
|
total += written;
|
||||||
|
|
||||||
|
if(storage_file_get_error(file) != FSE_OK || written != chunk) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(total != to_write);
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start) {
|
bool storage_file_seek(File* file, uint32_t offset, bool from_start) {
|
||||||
S_FILE_API_PROLOGUE;
|
S_FILE_API_PROLOGUE;
|
||||||
S_API_PROLOGUE;
|
S_API_PROLOGUE;
|
||||||
@@ -252,7 +287,7 @@ bool storage_file_exists(Storage* storage, const char* path) {
|
|||||||
return exist;
|
return exist;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool storage_file_copy_to_file(File* source, File* destination, uint32_t size) {
|
bool storage_file_copy_to_file(File* source, File* destination, size_t size) {
|
||||||
uint8_t* buffer = malloc(FILE_BUFFER_SIZE);
|
uint8_t* buffer = malloc(FILE_BUFFER_SIZE);
|
||||||
|
|
||||||
while(size) {
|
while(size) {
|
||||||
@@ -431,17 +466,22 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(storage_dir_exists(storage, old_path)) {
|
if(storage_dir_exists(storage, old_path)) {
|
||||||
FuriString* dir_path = furi_string_alloc_set_str(old_path);
|
// Cannot overwrite a file with a directory
|
||||||
if(!furi_string_end_with_str(dir_path, "/")) {
|
if(storage_file_exists(storage, new_path)) {
|
||||||
furi_string_cat_str(dir_path, "/");
|
|
||||||
}
|
|
||||||
const char* dir_path_s = furi_string_get_cstr(dir_path);
|
|
||||||
if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) {
|
|
||||||
error = FSE_INVALID_NAME;
|
error = FSE_INVALID_NAME;
|
||||||
furi_string_free(dir_path);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
furi_string_free(dir_path);
|
|
||||||
|
// Cannot rename a directory to itself or to a nested directory
|
||||||
|
if(storage_common_equivalent_path(storage, old_path, new_path, true)) {
|
||||||
|
error = FSE_INVALID_NAME;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renaming a regular file to itself does nothing and always succeeds
|
||||||
|
} else if(storage_common_equivalent_path(storage, old_path, new_path, false)) {
|
||||||
|
error = FSE_OK;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(storage_file_exists(storage, new_path)) {
|
if(storage_file_exists(storage, new_path)) {
|
||||||
@@ -742,6 +782,27 @@ bool storage_common_exists(Storage* storage, const char* path) {
|
|||||||
return storage_common_stat(storage, path, &file_info) == FSE_OK;
|
return storage_common_stat(storage, path, &file_info) == FSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool storage_common_equivalent_path(
|
||||||
|
Storage* storage,
|
||||||
|
const char* path1,
|
||||||
|
const char* path2,
|
||||||
|
bool truncate) {
|
||||||
|
S_API_PROLOGUE;
|
||||||
|
|
||||||
|
SAData data = {
|
||||||
|
.cequivpath = {
|
||||||
|
.path1 = path1,
|
||||||
|
.path2 = path2,
|
||||||
|
.truncate = truncate,
|
||||||
|
.thread_id = furi_thread_get_current_id(),
|
||||||
|
}};
|
||||||
|
|
||||||
|
S_API_MESSAGE(StorageCommandCommonEquivalentPath);
|
||||||
|
S_API_EPILOGUE;
|
||||||
|
|
||||||
|
return S_RETURN_BOOL;
|
||||||
|
}
|
||||||
|
|
||||||
/****************** ERROR ******************/
|
/****************** ERROR ******************/
|
||||||
|
|
||||||
const char* storage_error_get_desc(FS_Error error_id) {
|
const char* storage_error_get_desc(FS_Error error_id) {
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ typedef struct {
|
|||||||
FuriThreadId thread_id;
|
FuriThreadId thread_id;
|
||||||
} SADataCResolvePath;
|
} SADataCResolvePath;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* path1;
|
||||||
|
const char* path2;
|
||||||
|
bool truncate;
|
||||||
|
FuriThreadId thread_id;
|
||||||
|
} SADataCEquivPath;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
} SADataError;
|
} SADataError;
|
||||||
@@ -99,6 +106,7 @@ typedef union {
|
|||||||
SADataCStat cstat;
|
SADataCStat cstat;
|
||||||
SADataCFSInfo cfsinfo;
|
SADataCFSInfo cfsinfo;
|
||||||
SADataCResolvePath cresolvepath;
|
SADataCResolvePath cresolvepath;
|
||||||
|
SADataCEquivPath cequivpath;
|
||||||
|
|
||||||
SADataError error;
|
SADataError error;
|
||||||
|
|
||||||
@@ -142,6 +150,7 @@ typedef enum {
|
|||||||
StorageCommandSDStatus,
|
StorageCommandSDStatus,
|
||||||
StorageCommandCommonResolvePath,
|
StorageCommandCommonResolvePath,
|
||||||
StorageCommandSDMount,
|
StorageCommandSDMount,
|
||||||
|
StorageCommandCommonEquivalentPath,
|
||||||
} StorageCommand;
|
} StorageCommand;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void storage_path_trim_trailing_slashes(FuriString* path) {
|
||||||
|
while(furi_string_end_with(path, "/")) {
|
||||||
|
furi_string_left(path, furi_string_size(path) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/******************* File Functions *******************/
|
/******************* File Functions *******************/
|
||||||
|
|
||||||
bool storage_process_file_open(
|
bool storage_process_file_open(
|
||||||
@@ -357,6 +363,8 @@ static FS_Error storage_process_common_remove(Storage* app, FuriString* path) {
|
|||||||
FS_Error ret = storage_get_data(app, path, &storage);
|
FS_Error ret = storage_get_data(app, path, &storage);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
if(ret != FSE_OK) break;
|
||||||
|
|
||||||
if(storage_path_already_open(path, storage)) {
|
if(storage_path_already_open(path, storage)) {
|
||||||
ret = FSE_ALREADY_OPEN;
|
ret = FSE_ALREADY_OPEN;
|
||||||
break;
|
break;
|
||||||
@@ -398,6 +406,31 @@ static FS_Error storage_process_common_fs_info(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
storage_process_common_equivalent_path(Storage* app, FuriString* path1, FuriString* path2) {
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const StorageType storage_type1 = storage_get_type_by_path(path1);
|
||||||
|
const StorageType storage_type2 = storage_get_type_by_path(path2);
|
||||||
|
|
||||||
|
// Paths on different storages are of course not equal
|
||||||
|
if(storage_type1 != storage_type2) break;
|
||||||
|
|
||||||
|
StorageData* storage;
|
||||||
|
const FS_Error status = storage_get_data(app, path1, &storage);
|
||||||
|
|
||||||
|
if(status != FSE_OK) break;
|
||||||
|
|
||||||
|
FS_CALL(
|
||||||
|
storage,
|
||||||
|
common.equivalent_path(furi_string_get_cstr(path1), furi_string_get_cstr(path2)));
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/****************** Raw SD API ******************/
|
/****************** Raw SD API ******************/
|
||||||
// TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage
|
// TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage
|
||||||
#include "storages/storage_ext.h"
|
#include "storages/storage_ext.h"
|
||||||
@@ -649,6 +682,23 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) {
|
|||||||
app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true);
|
app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case StorageCommandCommonEquivalentPath: {
|
||||||
|
FuriString* path1 = furi_string_alloc_set(message->data->cequivpath.path1);
|
||||||
|
FuriString* path2 = furi_string_alloc_set(message->data->cequivpath.path2);
|
||||||
|
storage_path_trim_trailing_slashes(path1);
|
||||||
|
storage_path_trim_trailing_slashes(path2);
|
||||||
|
storage_process_alias(app, path1, message->data->cequivpath.thread_id, false);
|
||||||
|
storage_process_alias(app, path2, message->data->cequivpath.thread_id, false);
|
||||||
|
if(message->data->cequivpath.truncate) {
|
||||||
|
furi_string_left(path2, furi_string_size(path1));
|
||||||
|
}
|
||||||
|
message->return_data->bool_value =
|
||||||
|
storage_process_common_equivalent_path(app, path1, path2);
|
||||||
|
furi_string_free(path1);
|
||||||
|
furi_string_free(path2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// SD operations
|
// SD operations
|
||||||
case StorageCommandSDFormat:
|
case StorageCommandSDFormat:
|
||||||
message->return_data->error_value = storage_process_sd_format(app);
|
message->return_data->error_value = storage_process_sd_format(app);
|
||||||
|
|||||||
@@ -596,6 +596,16 @@ static FS_Error storage_ext_common_fs_info(
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool storage_ext_common_equivalent_path(const char* path1, const char* path2) {
|
||||||
|
#ifdef FURI_RAM_EXEC
|
||||||
|
UNUSED(path1);
|
||||||
|
UNUSED(path2);
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return strcasecmp(path1, path2) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/******************* Init Storage *******************/
|
/******************* Init Storage *******************/
|
||||||
static const FS_Api fs_api = {
|
static const FS_Api fs_api = {
|
||||||
.file =
|
.file =
|
||||||
@@ -624,6 +634,7 @@ static const FS_Api fs_api = {
|
|||||||
.mkdir = storage_ext_common_mkdir,
|
.mkdir = storage_ext_common_mkdir,
|
||||||
.remove = storage_ext_common_remove,
|
.remove = storage_ext_common_remove,
|
||||||
.fs_info = storage_ext_common_fs_info,
|
.fs_info = storage_ext_common_fs_info,
|
||||||
|
.equivalent_path = storage_ext_common_equivalent_path,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -686,6 +686,10 @@ static FS_Error storage_int_common_fs_info(
|
|||||||
return storage_int_parse_error(result);
|
return storage_int_parse_error(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool storage_int_common_equivalent_path(const char* path1, const char* path2) {
|
||||||
|
return strcmp(path1, path2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/******************* Init Storage *******************/
|
/******************* Init Storage *******************/
|
||||||
static const FS_Api fs_api = {
|
static const FS_Api fs_api = {
|
||||||
.file =
|
.file =
|
||||||
@@ -714,6 +718,7 @@ static const FS_Api fs_api = {
|
|||||||
.mkdir = storage_int_common_mkdir,
|
.mkdir = storage_int_common_mkdir,
|
||||||
.remove = storage_int_common_remove,
|
.remove = storage_int_common_remove,
|
||||||
.fs_info = storage_int_common_fs_info,
|
.fs_info = storage_int_common_fs_info,
|
||||||
|
.equivalent_path = storage_int_common_equivalent_path,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ static bool storage_settings_scene_bench_write(
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
storage_settings_scene_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) {
|
storage_settings_scene_bench_read(Storage* api, size_t size, uint8_t* data, uint32_t* speed) {
|
||||||
File* file = storage_file_alloc(api);
|
File* file = storage_file_alloc(api);
|
||||||
bool result = true;
|
bool result = true;
|
||||||
*speed = -1;
|
*speed = -1;
|
||||||
@@ -82,7 +82,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
|||||||
bench_data[i] = (uint8_t)i;
|
bench_data[i] = (uint8_t)i;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024};
|
size_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024};
|
||||||
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||||
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "storage_move_to_sd.h"
|
#include "storage_move_to_sd.h"
|
||||||
|
|
||||||
#include <core/common_defines.h>
|
#include <core/common_defines.h>
|
||||||
#include <core/log.h>
|
#include <core/log.h>
|
||||||
#include "loader/loader.h"
|
#include <loader/loader.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <toolbox/dir_walk.h>
|
#include <toolbox/dir_walk.h>
|
||||||
#include <toolbox/path.h>
|
#include <toolbox/path.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) {
|
|||||||
|
|
||||||
update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0);
|
update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0);
|
||||||
uint8_t* fw_block = malloc(FLASH_PAGE_SIZE);
|
uint8_t* fw_block = malloc(FLASH_PAGE_SIZE);
|
||||||
uint16_t bytes_read = 0;
|
size_t bytes_read = 0;
|
||||||
uint32_t element_offs = 0;
|
uint32_t element_offs = 0;
|
||||||
|
|
||||||
while(element_offs < stack_size) {
|
while(element_offs < stack_size) {
|
||||||
|
|||||||
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_0.png
vendored
Normal file
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_0.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_1.png
vendored
Normal file
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_10.png
vendored
Normal file
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_10.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user