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

Compare commits

...

174 Commits

Author SHA1 Message Date
MX
05b183f5e5 Merge branch 'dev' into release 2023-12-11 01:37:11 +03:00
MX
0b356c2d6d update changelog 2023-12-11 01:36:54 +03:00
MX
ee9092c8e5 Merge branch 'nfc-parsers' into dev 2023-12-08 00:18:23 +03:00
Methodius
2e7bb26eef Merge branch 'dev' into nfc-parsers 2023-12-07 22:22:29 +09:00
Methodius
5d28939c28 social_moscow parser verification collisions fix 2023-12-07 21:59:27 +09:00
MX
15a29e1483 format 2023-12-07 08:21:32 +03:00
MX
c22b3b57bd Merge branch 'nfc-parsers' into dev 2023-12-07 08:03:43 +03:00
MX
1daa2fa377 Merge branch 'ofw_dev' into dev 2023-12-07 08:03:18 +03:00
Methodius
1b45b8a17d Kazan parser: adult social tariff added 2023-12-06 18:25:47 +09:00
Methodius
e94beff204 Kazan parser: basic tariffs parsing added 2023-12-06 18:08:20 +09:00
gornekich
82baf1e923 [FL-3701] NFC fixes (#3264)
* nfc app: fix unlock with manual password crash
* nfc app: preserve card detected state
* nfc app: fix mf keys scene switch
* nfc app: fix multiple protocol tag detect notification
* nfc plugin: fix retrun in function body in aime parser
* iso14443-3b poller: rework ATTRIB response check
* nfc app: fix navigation after file load failur
* iso14443-3b poller: fix PVS warning
* mfc listener: add crutch in mfc emulation
2023-12-05 22:40:06 +09:00
MX
c416041379 Fix secplus v1 key display issue 2023-12-05 03:40:12 +03:00
MX
10444b943e Fix keeloq decode logic 2023-12-05 03:34:31 +03:00
MMX
2308a54ada Merge pull request #662 from noproto/dev
Add Saflok and MyKey KDFs
2023-12-05 03:33:37 +03:00
MMX
0ed6738a5b Merge pull request #660 from hnlcory/dev
Add keys to mf_classic_dict.nfc
2023-12-05 03:33:03 +03:00
noproto
dc25bfb831 Add Saflok and MyKey KDFs 2023-12-04 16:03:32 -05:00
MX
d6fcb04aa8 Merge branch 'ofw_dev' into dev 2023-12-03 18:42:11 +03:00
MX
9bf8f1015d fix keeloq null pointer if unknown 2023-12-03 18:41:28 +03:00
Honghao Zeng
c477d1321a nfc: m1k-based Aime (non-AIC) card support (#3241)
Co-authored-by: gornekich <n.gorbadey@gmail.com>
2023-12-03 20:00:46 +09:00
MX
ec99b70b38 upd changelog 2023-12-02 23:55:05 +03:00
MX
06a58ebd53 Merge branch 'ofw_dev' into dev [ci skip] 2023-12-02 23:19:43 +03:00
Skorpionm
eb6fe0a4db SubGhz: fix count bit for detect gate_tx protocol (#3253)
Co-authored-by: あく <alleteam@gmail.com>
2023-12-02 16:34:02 +09:00
pborsutzki
c6a14e1a67 Fixed a zero allocation error when reading an iso15693 nfc tag with no additional blocks. (#3229)
Co-authored-by: gornekich <n.gorbadey@gmail.com>
2023-12-02 16:27:58 +09:00
Astra
04cead1fc5 [FL-3620] Add the "remove pairing" button to BLE hid (#3237)
Co-authored-by: あく <alleteam@gmail.com>
2023-12-02 14:03:10 +09:00
Astra
93732865ac [FL-3132] HID app: Add new function key icons (#3236)
* Add new function key icons
* Fix graphical glitches on the buttons

Co-authored-by: あく <alleteam@gmail.com>
2023-12-02 13:52:04 +09:00
RebornedBrain
6a5d63803a [FL-3675] Ntag21x write (#3246)
* New scenes for ultralight poller write mode
* Added new button and transition logic for write operation
   For now write is only possible for NTAG21x cards with default password and no AUTHLIM set
* Poller states extended
* Enums and datatypes extended for new poller mode
* Added mode field to poller instance datatype
* New states for poller added in order to implement write mode
* Added new event type for locked cards in order to simplify state flow
* New logic for poller write commands
* Scenes adjustments
* Scenes renamed
* New field added to poller instance
* Now we write in 'page per call' mode
* Now function takes callback return value into account
* Callback will be called only in write mode
* Event type added
* Log adjusted and start page to write set
* Logs added and check in now false at start, then it moves to true
* Now mf_ultralight_poller_handler_request_write_data halts card in case of check failure and stops poller
* All fail events now returns NfcCommandStop callback
* In case of fail we move back properly
* Remove garbage

Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
2023-12-02 13:45:47 +09:00
MX
159aef022b Fix keeloq decoding order 2023-12-01 22:51:21 +03:00
MX
f7c63c675b upd changelog 2023-12-01 20:38:01 +03:00
MX
99d657fcfb merge fixes 2023-12-01 20:33:14 +03:00
MX
00ceb2cd5d Merge branch 'ofw_dev' into dev 2023-12-01 20:29:16 +03:00
Augusto Zanellato
b51a754fd9 Mifare Classic nested auth support (#3238)
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2023-12-01 22:25:53 +09:00
Augusto Zanellato
c1e0d02afc ST25TB poller refining + write support (#3239)
* nfc: st25tb: rework async poller
* nfc: st25tb: introduce sync poller
* nfc: st25tb: add write support
* nfc: st25tb: rewrite poller to use better states
* nfc: st25tb: move to mode request state after success
* nfc: st25tb: minor bug fixes
* type wasn't properly set on ready event
* sending NfcCustomEventPollerFailure on St25tbPollerEventTypeFailure caused poller to being freed and ultimately resulted in a thread crash

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2023-12-01 18:42:00 +09:00
hedger
890c9e87ce [FL-3690] Libraries cleanup; u2f crypto rework to use mbedtls (#3234)
* examples: plugins: utilize fal_embedded
* libs: removed fnv1a_hash
* furi: added FURI_PACKED; apps, libs: changed to use FURI_PACKED
* lib: mbedtls: using custom config
* lib: toolbox: removed md5, switched to mbedtls
* targets: f18: link fix
* lib: added mbedtls_cfg.h
* apps: nfc: explicit dependency on libmbedtls
* u2f: reworking to mbedtls
* u2f: replaced sha256 & hmac with mbedtls
* u2f: functional rework using mbedtls
* libs: dropped micro-ecc
* u2f: dropped old implementation
* toolbox: removed sha256 impl
* mcheck() for mbedtls
* libs: removed libmisc; split into smaller libs
* apps: debug: fixed display_test
* apps: include cleanups
* fbt: fixed VERSIONCOMSTR
* furi: added FURI_CHECK_RETURN
* lib: removed qrcode
* cleanup
* fbt: lint_py+format_py: fixed excessive command length
* api: Removed bzero from f7
* api: Removed bzero from f18
* Bump API Symbols

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2023-12-01 18:16:48 +09:00
MX
961dd297dd upd changelog 2023-12-01 04:53:11 +03:00
MX
d675563271 sync anims 2023-12-01 04:47:54 +03:00
MX
e027d5c3e8 Add new API function for varitemlist
by Willy-JL

466e1f989f
2023-12-01 04:47:17 +03:00
MX
fdcfd5996b Merge branch 'ofw_dev' into dev 2023-12-01 02:33:41 +03:00
Astra
a849d49c92 [FL-3682] Add the secret door animation (#3233)
Co-authored-by: あく <alleteam@gmail.com>
2023-11-30 18:51:38 +09:00
Evgeny Stepanischev
ff129e524a Allows you to use UCS-2 in canvas_glyph_width (#3226)
* allows you to use UCS-2 in canvas_glyph_width
* Sync API Symbols

Co-authored-by: あく <alleteam@gmail.com>
2023-11-30 18:38:48 +09:00
Cory Parker
05479103a6 Update mf_classic_dict.nfc
Added in new mf classic keys
2023-11-29 21:41:10 -10:00
MX
ff41d262dc add 303.9 frequency 2023-11-29 16:18:20 +03:00
MX
8628a2c6a2 update changelog, release planned for next week 2023-11-29 03:26:05 +03:00
MX
30914678c9 ibutton FIX crash!!!
why??????
2023-11-29 00:20:15 +03:00
MX
c4bf1fe717 use char escaping 2023-11-28 04:39:46 +03:00
MX
1946aaf5e1 upd discord notify 2023-11-28 04:21:43 +03:00
MX
a7b2427007 Merge branch 'ofw_dev' into dev 2023-11-26 19:58:48 +03:00
Astra
f9101d8084 [FL-3686] Mifare Classic fixes (#3221)
* Update Mifare Classic generators to create more accuate data
* Check the transfer buffer validity for NACK
* Fix the AC issues
* CRC errors don't really affect emulation, checking for them isn't worth it
* Make ATQA logic a bit easier to understand
* mf classic: change log level
* mf classic: fix log level

Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
2023-11-26 17:20:49 +09:00
RebornedBrain
1c3cbec661 [FL-3640] NFC: Felica UID emulation (#3190)
* Added basic template of Felica listener
* Raw nfc felica listener functions
* Added functions to setup chip for felica listener
* Cleanup function templates from unnecessary parts
* Removed todo comment
* Updated api versions
* Adjusted chip config for felica
* Set proper chip passive target mode for felica
* Added felica function to unit tests
* Update furi_hal_nfc_felica.c
* Removed duplication

Co-authored-by: gornekich <n.gorbadey@gmail.com>
2023-11-26 17:10:33 +09:00
MX
cea423742b Merge remote-tracking branch 'origin/nfc-parsers' into dev 2023-11-24 02:56:39 +03:00
Methodius
8c5f28d6a0 Kazan parser: Type: abonnement fix 2023-11-23 23:59:06 +09:00
Methodius
6c2e332638 Kazan transport cards parser added 2023-11-23 22:19:26 +09:00
Methodius
5f18532a59 Umarsh code cleanup 2023-11-23 22:11:12 +09:00
Methodius
63f072a819 Umarsh header check rework reworked. Thanks J for idea! 2023-11-22 17:26:10 +09:00
Methodius
df1d6503c0 Umarsh header check rework 2023-11-22 16:15:53 +09:00
MMX
972054b377 Merge pull request #655 from DarkFlippers/nfc-parsers
Nfc parsers
2023-11-22 01:50:54 +03:00
MX
e1c89834df no verbose 2023-11-22 01:48:22 +03:00
assasinfil
c145cad653 Verify card bugfix 2023-11-21 22:34:29 +03:00
Methodius
4261063c99 Metromoney transport card parser added 2023-11-22 00:39:23 +09:00
MX
f8546937c0 fix readme and feature name 2023-11-21 15:59:38 +03:00
assasinfil
3daaea6ecf Added new social card 2023-11-21 15:03:46 +03:00
assasinfil
4a84fbc6e3 UI printing bugfix 2023-11-21 13:12:24 +03:00
assasinfil
d8800e9fe3 Merge branch 'nfc-parsers' of https://github.com/DarkFlippers/unleashed-firmware into nfc-parsers 2023-11-21 11:54:11 +03:00
assasinfil
7fd921227c Fixes 2023-11-21 11:54:06 +03:00
Methodius
b6ad07b47c Umarsh parser: kopecks support added 2023-11-21 13:18:18 +09:00
Methodius
1a21f0e3c9 Umarsh transport cards parser added 2023-11-21 12:21:22 +09:00
MX
c666368446 add simple nfc file name display
temp fix for UI
2023-11-21 01:53:20 +03:00
MX
91d614dd25 add subghz honeywell protocol
by htotoo

https://github.com/Flipper-XFW/Xtreme-Firmware/commits?author=htotoo
2023-11-21 01:00:15 +03:00
MMX
baca59927b Merge pull request #653 from DarkFlippers/nfc-parsers
Nfc parsers
2023-11-20 23:25:52 +03:00
MX
4eb40ce948 fix ibutton info display
by @krolchonok
+ added missing furi_string_free
a378db6663/applications/main/ibutton/scenes/ibutton_scene_info.c
2023-11-20 23:25:29 +03:00
assasinfil
00b1018e9e Fixed validating 2023-11-20 20:46:51 +03:00
assasinfil
9c92338ddf Added social card parser 2023-11-20 19:11:30 +03:00
assasinfil
8188b63522 cleanup 2023-11-20 18:43:23 +03:00
assasinfil
0e172b67eb Layout 1c4 fix 2023-11-20 18:22:17 +03:00
assasinfil
156948ec58 All code added to parser 2023-11-20 17:42:19 +03:00
Methodius
bbdda5a3d7 trying to fix previous fix of timestamp_to_datetime() 2023-11-20 22:19:29 +09:00
Methodius
51a2e638ed trying to fix timestamp_to_datetime() 2023-11-20 22:14:49 +09:00
assasinfil
ffd9d3c218 Added msk transport parser 2023-11-20 14:10:50 +03:00
MX
9513ff5307 fix rgb patch 2023-11-17 02:43:47 +03:00
MX
12e736b283 fix ci, temp workaround for manifest 2023-11-17 02:35:27 +03:00
MX
a64c9534e2 Merge branch 'ofw_dev' into dev 2023-11-17 02:20:27 +03:00
Sergey Gavrilov
4b3e8aba29 [FL-3664] 64k does not enough (#3216)
* Unit tests: add "exists" to furi_record tests
* Unit tests: mu_warn, storage 64k test
* Storage: read/write over 64k
* Unit tests: moar tests for storage r/w for >64k cases
* Apps, libs: replace uint16_t with size_t on storage r/w operations
* Unit tests: better data pattern, subghz: warning if transmission is prohibited

Co-authored-by: あく <alleteam@gmail.com>
2023-11-16 01:39:27 +09:00
hedger
98d5718ec9 fbt: improvements (#3217)
* fbt: changed cdefines & lib handling for external apps; added extra checks for app manifest fields; moved around AppsC generator
* fbt: commandline fixes for spaces in paths
* fbt: fixed stringification for FAP_VERSION
* fbt: Removed excessive quoting for gdb
* docs: update for cdefines; fbt: typo fix
* fbt: enforcing at least 2 components in app version=

Co-authored-by: あく <alleteam@gmail.com>
2023-11-16 01:27:35 +09:00
あく
457aa5331f Various Fixes for 0.95 (#3215)
* FuriHal: retry gauge/charger initialization
* FuriHal: lower logging level for flash known errata
* FuriHal: graceful fail if subghz chip is not working
* Furi: issue stop command even if timer is not active, document timer behavior
2023-11-15 20:11:05 +04:00
Flipper Zelebro
a61b5d4b4c Add Mastercode SubGHz Protocol (#3187)
* Add Mastercode SubGHz Protocol
* Add 2 valid raw files and cleanup code
* Add tests to the two Raw Files
* Remove extra test & delete comments
* Fixes pulse length and shows correct Key

Co-authored-by: FlipperZelebro <flipperzelebro [at] gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
2023-11-15 18:04:45 +09:00
Georgii Surkov
ba074068b0 [FL-3662] Do not remove file when renaming to itself (#3193)
* Do not allow overwriting a file with dir and support renaming file to itself
* Fix operator precedence error
* Add support for storage-specific path equivalence checks
* Fix typo
* Fix updater compilation
* Update Doxygen comments in storage.h

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2023-11-15 17:56:13 +09:00
Georgii Surkov
615a147973 [FL-3608] Fix iButton crash on missing file (#3210)
Co-authored-by: あく <alleteam@gmail.com>
2023-11-15 17:45:41 +09:00
gornekich
c00776ca22 [FL-3666] NFC API improvements (#3214)
* drivers: expose st25r3916 driver API
* nfc poller: add start with custom callback
* mf classic: rework sync API with poller custom start
* mf ultralight: rework sync API with poller custom start
* iso14443_3a poller: remove unused col res state
* nfc: rework nfc poller custom start
* mf ultralight: rename sync API
* mf classic: rename sync API
* iso14443-3a: rename sync API
* nfc: remove async prefix in internal functions
* nfc: expose internal API
* nfc: fix sync api include and docs
* targets: fix f18 build
* nfc: rework NfcGenericEventEx type
* nfc poller: add documentation
* iso14443-3a poller: add documentation
* felica poller: add documentation
* iso14443_3b poller: add documentation
* so14443_4a poller: add documentation
* iso14443_4b poller: add documentation
* iso15693 poller: add documentation
* slix poller: add documentation
* mf desfire poller: add documentation
* mf ultralight poller: fix API and add documentation
* mf classic poller: add documentation

Co-authored-by: あく <alleteam@gmail.com>
2023-11-15 17:32:45 +09:00
RebornedBrain
d0b9a3a4ae [NFC] MF Ultralight no pwd polling adjustment (#3207)
* Listener log level changed to Trace
* Show pages count without pwd pages in case of no auth success
* Fixed unit tests

Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
2023-11-15 17:02:35 +09:00
Tobias Jost
dc246ddb09 Fix limited_credit_value having wrong value in mf_desfire_file_settings_parse (#3204)
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: あく <alleteam@gmail.com>
2023-11-15 16:39:29 +09:00
MMX
591a2f2b02 Merge pull request #649 from krolchonok/ibutton_fix
Fix naming in iButton
2023-11-13 20:01:09 +03:00
MX
7aaa847835 Merge branch 'nfcrefactoring' into dev 2023-11-13 20:57:36 +04:00
ushastoe
42101c6594 Revert " applications_user/Flipper-Zero-Privet-Mir-main/"
This reverts commit 3e9ecd2f4f.
2023-11-13 11:27:27 +03:00
ushastoe
3e9ecd2f4f applications_user/Flipper-Zero-Privet-Mir-main/ 2023-11-13 11:26:36 +03:00
ushastoe
725811d7b3 fix [] 2023-11-10 23:29:11 +03:00
ushastoe
a56a52b4bb Fix naming in iButton 2023-11-10 21:15:33 +03:00
MX
da18305119 Merge branch 'dev' into release 2023-10-18 00:57:24 +03:00
MX
c71d04a660 Merge branch 'dev' into release 2023-10-11 01:39:19 +03:00
MX
f3f68fd5d3 Merge branch 'dev' into release 2023-09-12 18:59:41 +03:00
MX
d4ce47c941 Merge branch 'dev' into release 2023-09-12 18:56:27 +03:00
MX
7413a78013 Merge branch 'dev' into release 2023-09-12 01:19:43 +03:00
MX
83967d6e06 Merge branch 'dev' into release 2023-09-12 00:46:22 +03:00
MX
4d1c6a8d20 Merge branch 'dev' into release 2023-09-05 06:11:28 +03:00
MX
c95a0a0171 Merge branch 'dev' into release 2023-09-04 23:19:37 +03:00
MX
feba1dbc24 Merge branch 'dev' into release 2023-08-20 05:51:14 +03:00
MX
5b578cf69d Merge branch 'dev' into release 2023-08-17 04:44:44 +03:00
MX
dafea08581 Merge branch 'dev' into release 2023-08-17 04:35:36 +03:00
MX
5c50224571 del submodules 2023-08-17 04:22:18 +03:00
MX
b510df47f9 Merge branch 'dev' into release 2023-08-17 04:21:28 +03:00
MX
2aec3ec5fd Merge branch 'dev' into release 2023-08-17 04:16:57 +03:00
MX
4b8c017302 Merge branch 'dev' into release 2023-08-05 19:09:50 +03:00
MX
b579bca227 Merge branch 'dev' into release 2023-07-25 05:35:30 +03:00
MX
3a676f7afa Merge branch 'dev' into release 2023-07-24 03:18:54 +03:00
MX
ca479303a1 Merge branch 'dev' into release 2023-07-20 01:32:12 +03:00
MX
5e649d8c41 Merge branch 'dev' into release 2023-07-20 01:25:14 +03:00
MX
0bc626ba1d Merge branch 'dev' into release 2023-07-09 00:59:02 +03:00
MX
3821ee7709 Merge branch 'dev' into release 2023-07-08 22:53:03 +03:00
MX
cb17189b15 Merge branch 'dev' into release 2023-06-25 01:40:11 +03:00
MX
80e8167054 Merge branch 'dev' into release 2023-06-25 00:55:18 +03:00
MX
eede5ed29e Merge branch 'dev' into release 2023-06-14 19:18:00 +03:00
MX
360fef7777 Merge branch 'dev' into release 2023-06-12 05:22:36 +03:00
MX
b83da5d3cb Merge branch 'dev' into release 2023-06-12 04:58:39 +03:00
MX
ae5d28fbc5 Merge branch 'dev' into release 2023-06-12 04:33:34 +03:00
MX
f4cd7c0100 Merge branch 'dev' into release 2023-06-03 23:47:45 +03:00
MX
7b68fd30ec Merge branch 'dev' into release 2023-06-03 22:38:42 +03:00
MX
ac222f1b0c Merge branch 'dev' into release 2023-06-03 02:23:14 +03:00
MX
b0e8e68909 Merge branch 'dev' into release 2023-06-02 19:52:05 +03:00
MX
ef5f6e2f70 Merge branch 'dev' into release 2023-05-28 07:01:32 +03:00
MX
30f6da3fa3 Merge branch 'dev' into release 2023-05-18 03:17:03 +03:00
MX
3a47154cdb Merge branch 'dev' into release 2023-05-18 02:54:58 +03:00
MX
d953d35991 Merge branch 'dev' into release 2023-05-05 23:33:23 +03:00
MX
4b74d13e10 Merge branch 'dev' into release 2023-05-02 03:40:11 +03:00
MX
98bf353287 Merge branch 'dev' into release 2023-04-27 22:15:19 +03:00
MX
eb8c751b31 Merge branch 'dev' into release 2023-04-27 19:58:25 +03:00
MX
9abad8704f Merge branch 'dev' into release 2023-04-27 04:02:39 +03:00
MX
6716c0f792 Merge branch 'dev' into release 2023-04-21 15:59:36 +03:00
MX
35f7ec6c07 Merge branch 'dev' into release 2023-04-14 04:08:34 +03:00
MX
a524fd7674 Merge branch 'dev' into release 2023-04-07 11:50:22 +03:00
MX
ff27fd3094 Merge branch 'dev' into release 2023-04-07 01:08:58 +03:00
MX
977ac09fe6 Merge branch 'dev' into release 2023-04-07 01:07:08 +03:00
MX
1a88e01899 Merge branch 'dev' into release 2023-04-02 03:12:58 +03:00
MX
6e710c5164 Merge branch 'dev' into release 2023-03-28 04:54:07 +03:00
MX
fd56ac3400 Merge branch 'dev' into release 2023-03-23 00:42:20 +03:00
MX
1d801c38f9 Merge branch 'dev' into release 2023-03-21 03:25:06 +03:00
MX
8afdb5b7b4 Merge branch 'dev' into release 2023-03-21 03:06:21 +03:00
MX
83624b1dee Merge branch 'dev' into release 2023-03-21 02:59:07 +03:00
MX
56adcf1ad8 Merge branch 'dev' into release 2023-03-18 06:44:59 +03:00
MX
ad27f87a0c Fix submodules 2023-03-17 04:18:15 +03:00
MX
346cf299ee Merge branch 'dev' into release 2023-03-17 04:15:27 +03:00
MX
05489fda7d Merge branch 'dev' into release 2023-03-09 05:08:50 +03:00
MX
1b12526357 Merge branch 'dev' into release 2023-03-01 03:11:36 +03:00
MX
802035d92e Merge branch 'dev' into release 2023-02-26 06:51:31 +03:00
MX
a24d0f1958 Merge branch 'dev' into release 2023-02-24 06:12:29 +03:00
MX
da68f2e4ed Merge branch 'dev' into release 2023-02-13 22:07:34 +03:00
MX
8f16dbb8e7 Merge branch 'dev' into release 2023-02-13 01:18:46 +03:00
MX
49e458f1b5 Merge branch 'dev' into release 2023-02-12 05:07:09 +03:00
MX
5cf46d2aa9 Merge branch 'dev' into release 2023-02-11 04:56:15 +03:00
MX
beedf54e75 Merge branch 'dev' into release 2023-02-11 04:35:02 +03:00
MX
70ccb89c3d fix submodule update 2023-02-04 23:52:13 +03:00
MX
5ea43a2a4b Merge branch 'dev' into release 2023-02-04 23:50:09 +03:00
MX
41f60dbbf4 Merge branch 'dev' into release 2023-01-19 22:45:38 +03:00
MX
827341ec08 Merge branch 'dev' into release 2023-01-19 01:53:01 +03:00
MX
5c36043d03 Merge branch 'dev' into release 2022-12-31 03:21:55 +03:00
MX
cf5811f8d9 Merge branch 'dev' into release 2022-12-31 02:59:26 +03:00
MX
ec6a169bf8 Merge branch 'dev' into release 2022-12-19 21:57:35 +03:00
MX
f1dec87c1b Merge branch 'dev' into release 2022-12-19 20:52:17 +03:00
MX
ab29951a99 Merge branch 'dev' into release 2022-12-19 19:26:06 +03:00
MX
bbe9f88bbe Merge branch 'dev' into release 2022-12-11 01:34:50 +03:00
MX
9188bf0013 Merge branch 'dev' into release 2022-12-10 04:38:04 +03:00
MX
f33ed59567 Merge branch 'dev' into release 2022-12-03 01:04:48 +03:00
MX
3fd8c80861 Merge branch 'dev' into release 2022-12-03 00:59:18 +03:00
MX
7b8ac3a5a0 Merge branch 'dev' into release 2022-11-30 21:05:08 +03:00
MX
6fef957001 Merge branch 'dev' into release 2022-11-24 04:02:59 +03:00
MX
0de1c9df89 Merge branch 'dev' into release 2022-11-24 03:45:52 +03:00
MX
a0e8cfbe97 Merge branch 'dev' into release 2022-11-24 03:41:01 +03:00
323 changed files with 10800 additions and 11898 deletions

View File

@@ -175,3 +175,10 @@ Max butthurt: 12
Min level: 2
Max level: 3
Weight: 4
Name: L2_Secret_door_128x64
Min butthurt: 0
Max butthurt: 12
Min level: 2
Max level: 3
Weight: 4

View File

@@ -462,10 +462,10 @@ index 0000000..68dacda
+ */
+const char* rgb_backlight_get_color_text(uint8_t index);
\ 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
--- a/firmware/targets/f7/furi_hal/furi_hal_light.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_light.c
--- a/targets/f7/furi_hal/furi_hal_light.c
+++ b/targets/f7/furi_hal/furi_hal_light.c
@@ -3,6 +3,7 @@
#include <furi_hal_light.h>
#include <lp5562.h>

View File

@@ -45,7 +45,9 @@ steps:
- export FORCE_NO_DIRTY=yes
- export FBT_GIT_SUBMODULE_SHALLOW=1
- 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/apps_data/* applications/main/clock_app/resources/apps_data/
- rm -rf base_pack_build
@@ -65,7 +67,8 @@ steps:
pull: never
commands:
- 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/
- rm -rf extra_pack_build
- export DIST_SUFFIX=${DRONE_TAG}e
@@ -117,7 +120,9 @@ steps:
- rm -f build/f7-firmware-C/toolbox/version.*
- ./fbt COMPACT=1 DEBUG=0 updater_package
- 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/apps_data/* applications/main/clock_app/resources/apps_data/
- rm -rf base_pack_build
@@ -336,9 +341,10 @@ steps:
DISCORD_WEBHOOK:
from_secret: dis_release_webhook
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
- ./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"
image: appleboy/drone-telegram
@@ -420,7 +426,9 @@ steps:
- export FORCE_NO_DIRTY=yes
- export FBT_GIT_SUBMODULE_SHALLOW=1
- 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/apps_data/* applications/main/clock_app/resources/apps_data/
- rm -rf base_pack_build
@@ -440,7 +448,8 @@ steps:
pull: never
commands:
- 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/
- rm -rf extra_pack_build
- export DIST_SUFFIX=${DRONE_BUILD_NUMBER}e
@@ -657,9 +666,10 @@ steps:
DISCORD_WEBHOOK:
from_secret: dis_dev_webhook
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
- ./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:
branch:

View File

@@ -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/*

View File

@@ -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
* SubGHz: Add 4 more systems to Add Manually (untested!)
* SubGHz: Add Manually fixes
* SubGHz: Added NiceFlor-S to ignore options, removed colons. (by @G2Dolphin | PR #620)
* 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)
* NFC: Added simple key name display to UI to fix regression
* 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
* RGB: Fix white color on reboot, move settings, add custom color option
* **BLE Spam app** updated to latest version (Android, Windows support) (by @Willy-JL) -> (app can be found in builds ` `, `e`, `n`, `r`)
* OFW: Fix double arrows and add proper indication
* OFW: SubGHz: add manually fix 12-bits is 0xFFF (or 0xFF0) CAME/NICE 12-bit
* OFW: Fix various crashes if debug libraries used
* Apps: **Bluetooth Remote / USB Keyboard & Mouse** - `Movie` and `PTT` modes by @hryamzik
* 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`)
* Apps: **NFC Magic** - Gen4 Actions (option to fix card with broken config) (by @Leptopt1los and @xMasterX)
* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
* 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`|
|TON||`EQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmpGf`|
#### Thanks to our sponsors:
callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
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!
@@ -50,7 +116,7 @@ What build I should download and what this name means - `flipper-z-f7-update-(ve
| `c` | ✅ | | | |
| `n` | | ✅ | | |
| `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!

View File

@@ -55,7 +55,7 @@
- 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)
- 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)
- External CC1101 module support (Hardware SPI used)
- **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.
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
- @assasinfil - SubGHz protocols
- @assasinfil - SubGHz protocols, NFC parsers (working with @Leptopt1los)
- @Svaarich - UI design and animations
- @amec0e & @Leptopt1los - Infrared assets
- @amec0e & @Leptopt1los (only ACs) - Infrared assets
- Community moderators in Telegram, Discord, and Reddit
- And of course our GitHub community. Your PRs are a very important part of this firmware and open-source development.
@@ -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
# 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`...
## Firmware & Development

View File

@@ -172,17 +172,19 @@ Alias("fap_dist", fap_dist)
fap_deploy = distenv.PhonyTarget(
"fap_deploy",
[
Action(
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/storage.py",
"-p",
"${FLIP_PORT}",
"send",
"${SOURCE}",
"/ext/apps",
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/storage.py",
"-p",
"${FLIP_PORT}",
"send",
"${SOURCE}",
"/ext/apps",
]
]
],
),
source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")),
)
Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"])
@@ -261,7 +263,7 @@ distenv.PhonyTarget(
distenv.PhonyTarget(
"debug_other_blackmagic",
"${GDBPYCOM}",
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
GDBPYOPTS=debug_other_opts,
)
@@ -276,23 +278,27 @@ distenv.PhonyTarget(
# Linter
distenv.PhonyTarget(
"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"]],
)
distenv.PhonyTarget(
"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"]],
)
# 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
firmware_env.Append(
PY_LINT_SOURCES=[
# Py code folders
"site_scons",
"scripts",
"applications",
"applications_user",
"assets",
"targets",
# Extra files
"SConstruct",
"firmware.scons",
@@ -302,7 +308,10 @@ firmware_env.Append(
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(
"lint_py",
@@ -323,10 +332,14 @@ distenv.PhonyTarget(
)
# 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
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
@@ -361,5 +374,5 @@ distenv.Alias("vscode_dist", vscode_dist)
# Configure shell with build tools
distenv.PhonyTarget(
"env",
"@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)",
"@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)",
)

View File

@@ -4,7 +4,6 @@ App(
apptype=FlipperAppType.DEBUG,
targets=["f7"],
entry_point="accessor_app",
cdefines=["APP_ACCESSOR"],
requires=["gui"],
stack_size=4 * 1024,
order=40,

View File

@@ -3,7 +3,6 @@ App(
name="Battery Test",
apptype=FlipperAppType.DEBUG,
entry_point="battery_test_app",
cdefines=["APP_BATTERY_TEST"],
requires=[
"gui",
"power",

View File

@@ -3,7 +3,6 @@ App(
name="Blink Test",
apptype=FlipperAppType.DEBUG,
entry_point="blink_test_app",
cdefines=["APP_BLINK"],
requires=["gui"],
stack_size=1 * 1024,
order=10,

View File

@@ -3,7 +3,6 @@ App(
name="CCID Debug",
apptype=FlipperAppType.DEBUG,
entry_point="ccid_test_app",
cdefines=["CCID_TEST"],
requires=[
"gui",
],

View File

@@ -13,12 +13,12 @@ struct ISO7816_Command_APDU {
//body
uint8_t Lc;
uint8_t Le;
} __attribute__((packed));
} FURI_PACKED;
struct ISO7816_Response_APDU {
uint8_t SW1;
uint8_t SW2;
} __attribute__((packed));
} FURI_PACKED;
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
void iso7816_read_command_apdu(

View File

@@ -3,7 +3,6 @@ App(
name="Crash Test",
apptype=FlipperAppType.DEBUG,
entry_point="crash_test_app",
cdefines=["APP_CRASH_TEST"],
requires=["gui"],
stack_size=1 * 1024,
fap_category="Debug",

View File

@@ -3,9 +3,8 @@ App(
name="Display Test",
apptype=FlipperAppType.DEBUG,
entry_point="display_test_app",
cdefines=["APP_DISPLAY_TEST"],
requires=["gui"],
fap_libs=["misc"],
fap_libs=["u8g2"],
stack_size=1 * 1024,
order=120,
fap_category="Debug",

View File

@@ -3,7 +3,6 @@ App(
name="File Browser Test",
apptype=FlipperAppType.DEBUG,
entry_point="file_browser_app",
cdefines=["APP_FILE_BROWSER_TEST"],
requires=["gui"],
stack_size=2 * 1024,
order=150,

View File

@@ -3,7 +3,6 @@ App(
name="Keypad Test",
apptype=FlipperAppType.DEBUG,
entry_point="keypad_test_app",
cdefines=["APP_KEYPAD_TEST"],
requires=["gui"],
stack_size=1 * 1024,
order=30,

View File

@@ -3,7 +3,6 @@ App(
name="Locale Test",
apptype=FlipperAppType.DEBUG,
entry_point="locale_test_app",
cdefines=["APP_LOCALE"],
requires=["gui", "locale"],
stack_size=2 * 1024,
order=70,

View File

@@ -3,7 +3,6 @@ App(
name="Text Box Test",
apptype=FlipperAppType.DEBUG,
entry_point="text_box_test_app",
cdefines=["APP_TEXT_BOX_TEST"],
requires=["gui"],
stack_size=1 * 1024,
order=140,

View File

@@ -3,7 +3,6 @@ App(
name="UART Echo",
apptype=FlipperAppType.DEBUG,
entry_point="uart_echo_app",
cdefines=["APP_UART_ECHO"],
requires=["gui"],
stack_size=2 * 1024,
order=70,

View File

@@ -3,18 +3,29 @@
#include <furi.h>
#include "../minunit.h"
void test_furi_create_open() {
// 1. Create record
uint8_t test_data = 0;
furi_record_create("test/holding", (void*)&test_data);
#define TEST_RECORD_NAME "test/holding"
// 2. Open it
void* record = furi_record_open("test/holding");
void test_furi_create_open() {
// 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);
// 3. Close it
furi_record_close("test/holding");
// Close it
furi_record_close(TEST_RECORD_NAME);
// 4. Clean up
furi_record_destroy("test/holding");
// Clean up
furi_record_destroy(TEST_RECORD_NAME);
// Test that record does not exist
mu_check(furi_record_exists(TEST_RECORD_NAME) == false);
}

View File

@@ -81,6 +81,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
void minunit_print_progress(void);
void minunit_print_fail(const char* error);
void minunit_printf_warning(const char* format, ...);
/* Definitions */
#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);)
#define MU_EXIT_CODE minunit_fail
/* Warnings */
#define mu_warn(message) \
MU__SAFE_BLOCK(minunit_printf_warning("%s:%d: %s", __FILE__, __LINE__, message);)
/* Assertions */
#define mu_check(test) \
MU__SAFE_BLOCK( \

View File

@@ -7,10 +7,10 @@
#include <nfc/nfc_poller.h>
#include <nfc/nfc_listener.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_poller_sync_api.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <nfc/helpers/nfc_dict.h>
#include <nfc/nfc.h>
@@ -182,8 +182,8 @@ MU_TEST(iso14443_3a_reader) {
Iso14443_3aData iso14443_3a_poller_data = {};
mu_assert(
iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
"iso14443_3a_poller_read() failed");
iso14443_3a_poller_sync_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
"iso14443_3a_poller_sync_read() failed");
nfc_listener_stop(iso3_listener);
mu_assert(
@@ -203,15 +203,26 @@ static void mf_ultralight_reader_test(const char* path) {
NfcDevice* nfc_device = nfc_device_alloc();
mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n");
NfcListener* mfu_listener = nfc_listener_alloc(
listener,
NfcProtocolMfUltralight,
nfc_device_get_data(nfc_device, NfcProtocolMfUltralight));
MfUltralightData* data =
(MfUltralightData*)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);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
nfc_listener_free(mfu_listener);
@@ -259,8 +270,8 @@ MU_TEST(ntag_213_locked_reader) {
nfc_listener_start(mfu_listener, NULL, NULL);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
nfc_listener_free(mfu_listener);
@@ -297,8 +308,8 @@ static void mf_ultralight_write() {
MfUltralightData* mfu_data = mf_ultralight_alloc();
// Initial read
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
mu_assert(
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_hal_random_fill_buf(page.data, sizeof(MfUltralightPage));
mfu_data->page[i] = page;
error = mf_ultralight_poller_write_page(poller, i, &page);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_write_page() failed");
error = mf_ultralight_poller_sync_write_page(poller, i, &page);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_write_page() failed");
}
// Verification read
error = mf_ultralight_poller_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
const MfUltralightData* mfu_listener_data =
@@ -344,7 +355,7 @@ static void mf_classic_reader() {
MfClassicBlock block = {};
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_free(mfc_listener);
@@ -372,8 +383,8 @@ static void mf_classic_write() {
furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock));
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
mf_classic_poller_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
mf_classic_poller_sync_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
nfc_listener_stop(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);
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");
int32_t data = 200;
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(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(new_value == value, "Value not match");

View File

@@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
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

View File

@@ -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

View File

@@ -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

View File

@@ -1,27 +1,31 @@
#include "flipper.pb.h"
#include <core/check.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 "../minunit.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 <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)
#define M_OPL_MsgList_t() LIST_OPLIST(MsgList)

View File

@@ -139,7 +139,7 @@ static bool write_file_13DA(Storage* storage, const char* path) {
File* file = storage_file_alloc(storage);
bool result = false;
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_free(file);

View File

@@ -115,6 +115,66 @@ MU_TEST(storage_file_open_close) {
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) {
storage_file_open_lock_setup();
MU_RUN_TEST(storage_file_open_close);
@@ -122,6 +182,10 @@ MU_TEST_SUITE(storage_file) {
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) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file;
@@ -640,6 +704,7 @@ MU_TEST_SUITE(test_md5_calc_suite) {
int run_minunit_test_storage() {
MU_RUN_SUITE(storage_file);
MU_RUN_SUITE(storage_file_64k);
MU_RUN_SUITE(storage_dir);
MU_RUN_SUITE(storage_rename);
MU_RUN_SUITE(test_data_path);

View File

@@ -324,6 +324,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
furi_hal_subghz_set_frequency_and_path(433920000);
if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) {
mu_warn("SubGHZ transmission is prohibited");
return false;
}
@@ -652,6 +653,13 @@ MU_TEST(subghz_decoder_kinggates_stylo4k_test) {
"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
MU_TEST(subghz_encoder_princeton_test) {
mu_assert(
@@ -803,6 +811,12 @@ MU_TEST(subghz_encoder_dooya_test) {
"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_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_nice_one_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_came_test);
@@ -879,6 +894,7 @@ MU_TEST_SUITE(subghz) {
MU_RUN_TEST(subghz_encoder_smc5326_test);
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
MU_RUN_TEST(subghz_encoder_dooya_test);
MU_RUN_TEST(subghz_encoder_mastercode_test);
MU_RUN_TEST(subghz_random_test);
subghz_test_deinit();

View File

@@ -78,6 +78,16 @@ void minunit_print_fail(const char* 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) {
UNUSED(cli);
UNUSED(args);

View File

@@ -3,7 +3,6 @@ App(
name="USB Mouse",
apptype=FlipperAppType.DEBUG,
entry_point="usb_mouse_app",
cdefines=["APP_USB_MOUSE"],
requires=["gui"],
stack_size=1 * 1024,
order=60,

View File

@@ -3,7 +3,6 @@ App(
name="USB Test",
apptype=FlipperAppType.DEBUG,
entry_point="usb_test_app",
cdefines=["APP_USB_TEST"],
requires=["gui"],
stack_size=1 * 1024,
order=50,

View File

@@ -3,7 +3,6 @@ App(
name="Vibro Test",
apptype=FlipperAppType.DEBUG,
entry_point="vibro_test_app",
cdefines=["APP_VIBRO_TEST"],
requires=["gui"],
stack_size=1 * 1024,
order=20,

View File

@@ -14,6 +14,7 @@ App(
entry_point="advanced_plugin1_ep",
requires=["example_advanced_plugins"],
sources=["plugin1.c"],
fal_embedded=True,
)
App(
@@ -22,4 +23,5 @@ App(
entry_point="advanced_plugin2_ep",
requires=["example_advanced_plugins"],
sources=["plugin2.c"],
fal_embedded=True,
)

View File

@@ -23,7 +23,10 @@ int32_t example_advanced_plugins_app(void* p) {
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
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");
break;
}

View File

@@ -12,12 +12,12 @@ static bool archive_favorites_read_line(File* file, FuriString* str_result) {
bool result = false;
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) {
return false;
}
for(uint16_t i = 0; i < read_count; i++) {
for(size_t i = 0; i < read_count; i++) {
if(buffer[i] == '\n') {
uint32_t position = storage_file_tell(file);
if(storage_file_get_error(file) != FSE_OK) {

View File

@@ -174,22 +174,21 @@ void ibutton_free(iButton* 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);
const bool success = ibutton_protocols_load(
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
if(!success) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
} else {
if(success) {
FuriString* tmp = furi_string_alloc();
path_extract_filename(ibutton->file_path, tmp, true);
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
furi_string_free(tmp);
} else if(show_error) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
}
return success;
@@ -210,7 +209,7 @@ bool ibutton_select_and_load_key(iButton* ibutton) {
if(!dialog_file_browser_show(
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
break;
success = ibutton_load_key(ibutton);
success = ibutton_load_key(ibutton, true);
} while(!success);
return success;
@@ -283,7 +282,7 @@ int32_t ibutton_app(void* arg) {
} else {
furi_string_set(ibutton->file_path, (const char*)arg);
key_loaded = ibutton_load_key(ibutton);
key_loaded = ibutton_load_key(ibutton, true);
}
}

View File

@@ -90,7 +90,7 @@ typedef enum {
} iButtonNotificationMessage;
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_delete_key(iButton* ibutton);
void ibutton_reset_key(iButton* ibutton);

View File

@@ -43,7 +43,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
} else if(event.type == SceneManagerEventTypeBack) {
// User cancelled editing, reload the key from storage
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(
scene_manager, iButtonSceneStart);
}

View File

@@ -23,15 +23,15 @@ void ibutton_scene_emulate_on_enter(void* context) {
furi_string_printf(
tmp,
"%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)));
"[%s]\n%s",
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)),
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name);
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, 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_start(ibutton->worker, key);

View File

@@ -8,21 +8,19 @@ void ibutton_scene_info_on_enter(void* context) {
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
FuriString* tmp = furi_string_alloc();
FuriString* keynumber = furi_string_alloc();
ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber);
furi_string_printf(
tmp,
"\e#%s [%s]\e#",
"\e#%s\n[%s]\e#\n%s",
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, 0, 2, 128, 12, 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));
widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
iButtonProtocolFeatureExtData) {
@@ -32,6 +30,7 @@ void ibutton_scene_info_on_enter(void* context) {
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
furi_string_free(keynumber);
}
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {

View File

@@ -26,7 +26,7 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.event == iButtonCustomEventRpcLoadFile) {
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);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);

View File

@@ -42,12 +42,12 @@ void ibutton_scene_write_on_enter(void* context) {
furi_string_printf(
tmp,
"%s\n[%s]",
ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
"[%s]\n%s ",
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
ibutton->key_name);
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);
@@ -63,7 +63,7 @@ void ibutton_scene_write_on_enter(void* context) {
}
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);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);

View File

@@ -13,7 +13,7 @@ App(
"!plugins",
"!nfc_cli.c",
],
fap_libs=["assets"],
fap_libs=["assets", "mbedtls"],
fap_icon="icon.png",
fap_category="NFC",
)
@@ -56,6 +56,15 @@ App(
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(
appid="plantain_parser",
apptype=FlipperAppType.PLUGIN,
@@ -74,6 +83,60 @@ App(
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(
appid="nfc_start",
targets=["f7"],

View File

@@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even
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 = {
.features = NfcProtocolFeatureNone,
.features = NfcProtocolFeatureEmulateUid,
.scene_info =
{
@@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
},
.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,
},
};

View File

@@ -12,6 +12,7 @@ enum {
SubmenuIndexUnlock = SubmenuIndexCommonMax,
SubmenuIndexUnlockByReader,
SubmenuIndexUnlockByPassword,
SubmenuIndexWrite,
};
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,
nfc_protocol_support_common_submenu_callback,
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) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
return true;
} else if(event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
return true;
}
return false;
}

View File

@@ -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");
furi_string_set(
temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
furi_string_cat_printf(temp_str, "\n%s", furi_string_get_cstr(instance->file_name));
}
widget_add_text_box_element(

View File

@@ -29,7 +29,9 @@ static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, v
NfcApp* instance = context;
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(
instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);

View File

@@ -400,15 +400,16 @@ bool nfc_load_from_file_select(NfcApp* instance) {
browser_options.base_path = NFC_APP_FOLDER;
browser_options.hide_dot_files = true;
// Input events and views are managed by file_browser
bool result = dialog_file_browser_show(
instance->dialogs, instance->file_path, instance->file_path, &browser_options);
bool success = false;
do {
// 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) {
result = nfc_load_file(instance, instance->file_path, true);
}
return result;
return success;
}
void nfc_show_loading_popup(void* context, bool show) {

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -4,7 +4,7 @@
#include <nfc/nfc_device.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"
@@ -91,7 +91,7 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
MfClassicAuthContext auth_context;
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) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
@@ -119,7 +119,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
do {
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;
data->type = type;
@@ -134,7 +134,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
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) {
FURI_LOG_W(TAG, "Failed to read data");
break;

View 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
#include <nfc/nfc_device.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"
@@ -49,7 +49,7 @@ bool two_cities_verify(Nfc* nfc) {
MfClassicAuthContext auth_ctx = {};
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) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
@@ -72,7 +72,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
do {
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;
data->type = type;
@@ -84,7 +84,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
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) {
FURI_LOG_W(TAG, "Failed to read data");
break;

View 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;
}

View File

@@ -3823,3 +3823,8 @@ F72CD208FDF9
2158E314C3DF
# 1k WALDORF ASTORIA
011C6CF459E8
# 1k HAWAII HOTEL
2CAD8A83DF28
555D8BBC2D3E
78DF1176C8FD
ADC169F922CB

View File

@@ -24,6 +24,10 @@ ADD_SCENE(nfc, field, Field)
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
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_warn, MfUltralightUnlockWarn)
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)

View File

@@ -37,6 +37,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventWorkerExit) {
if(instance->protocols_detected_num > 1) {
notification_message(instance->notifications, &sequence_single_vibro);
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
} else {
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);

View File

@@ -45,11 +45,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexMfClassicKeys) {
if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
} else {
scene_manager_previous_scene(instance->scene_manager);
}
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
consumed = true;
} else if(event.event == SubmenuIndexMfUltralightUnlock) {
mf_ultralight_auth_reset(instance->mf_ul_auth);

View File

@@ -52,11 +52,12 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
bool consumed = false;
nfc->protocols_detected[0] = nfc_device_get_protocol(nfc->nfc_device);
MfUltralightAuthType type = nfc->mf_ul_auth->type;
if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) {
if(event.type == SceneManagerEventTypeCustom) {
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);
dolphin_deed(DolphinDeedNfcRead);
consumed = true;

View 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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -21,7 +21,6 @@ void nfc_scene_select_protocol_on_enter(void* context) {
} else {
prefix = "Read as";
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++) {

View File

@@ -125,7 +125,6 @@ void dict_attack_reset(DictAttack* instance) {
instance->view,
DictAttackViewModel * model,
{
model->card_detected = false;
model->sectors_total = 0;
model->sectors_read = 0;
model->current_sector = 0;

View File

@@ -159,7 +159,7 @@ void subghz_scene_radio_settings_on_enter(void* context) {
item = variable_item_list_add(
variable_item_list,
"Time In Names",
"Protocol Names",
TIMESTAMP_NAMES_COUNT,
subghz_scene_receiver_config_set_timestamp_file_names,
subghz);

View File

@@ -21,13 +21,13 @@
#define MAX_HISTORY 4
static const uint32_t subghz_frequency_list[] = {
300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000, 309000000,
310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000, 314980000,
315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000, 390000000,
418000000, 430000000, 431000000, 431500000, 433075000, 433220000, 433420000, 433657070,
433889000, 433920000, 434075000, 434176948, 434390000, 434420000, 434775000, 438900000,
440175000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000, 906400000,
915000000, 925000000, 928000000};
300000000, 302757000, 303875000, 303900000, 304250000, 307000000, 307500000, 307800000,
309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000,
314980000, 315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000,
390000000, 418000000, 430000000, 431000000, 431500000, 433075000, 433220000, 433420000,
433657070, 433889000, 433920000, 434075000, 434176948, 434390000, 434420000, 434775000,
438900000, 440175000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000,
906400000, 915000000, 925000000, 928000000};
typedef enum {
SubGhzFrequencyAnalyzerStatusIDLE,

View File

@@ -7,7 +7,7 @@ App(
icon="A_U2F_14",
order=80,
resources="resources",
fap_libs=["assets"],
fap_libs=["assets", "mbedtls"],
fap_category="USB",
fap_icon="icon.png",
)

View File

@@ -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);
}

View File

@@ -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

View File

@@ -1,18 +1,22 @@
#include <furi.h>
#include "u2f.h"
#include "u2f_hid.h"
#include "u2f_data.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_random.h>
#include <littlefs/lfs_util.h> // for lfs_tobe32
#include "toolbox/sha256.h"
#include "hmac_sha256.h"
#include "micro-ecc/uECC.h"
#include <mbedtls/sha256.h>
#include <mbedtls/md.h>
#include <mbedtls/ecdsa.h>
#include <mbedtls/error.h>
#define TAG "U2f"
#define WORKER_TAG TAG "Worker"
#define MCHECK(expr) furi_check((expr) == 0)
#define U2F_CMD_REGISTER 0x01
#define U2F_CMD_AUTHENTICATE 0x02
#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
} 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 {
uint8_t format;
uint8_t xy[64];
} __attribute__((packed)) U2fPubKey;
} FURI_PACKED U2fPubKey;
_Static_assert(sizeof(U2fPubKey) == U2F_EC_POINT_SIZE, "U2fPubKey size mismatch");
typedef struct {
uint8_t len;
uint8_t hash[32];
uint8_t nonce[32];
} __attribute__((packed)) U2fKeyHandle;
uint8_t hash[U2F_HASH_SIZE];
uint8_t nonce[U2F_NONCE_SIZE];
} FURI_PACKED U2fKeyHandle;
typedef struct {
uint8_t cla;
@@ -42,16 +56,16 @@ typedef struct {
uint8_t p1;
uint8_t p2;
uint8_t len[3];
uint8_t challenge[32];
uint8_t app_id[32];
} __attribute__((packed)) U2fRegisterReq;
uint8_t challenge[U2F_CHALLENGE_SIZE];
uint8_t app_id[U2F_APP_ID_SIZE];
} FURI_PACKED U2fRegisterReq;
typedef struct {
uint8_t reserved;
U2fPubKey pub_key;
U2fKeyHandle key_handle;
uint8_t cert[];
} __attribute__((packed)) U2fRegisterResp;
} FURI_PACKED U2fRegisterResp;
typedef struct {
uint8_t cla;
@@ -59,16 +73,16 @@ typedef struct {
uint8_t p1;
uint8_t p2;
uint8_t len[3];
uint8_t challenge[32];
uint8_t app_id[32];
uint8_t challenge[U2F_CHALLENGE_SIZE];
uint8_t app_id[U2F_APP_ID_SIZE];
U2fKeyHandle key_handle;
} __attribute__((packed)) U2fAuthReq;
} FURI_PACKED U2fAuthReq;
typedef struct {
uint8_t user_present;
uint32_t counter;
uint8_t signature[];
} __attribute__((packed)) U2fAuthResp;
} FURI_PACKED U2fAuthResp;
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};
struct U2fData {
uint8_t device_key[32];
uint8_t cert_key[32];
uint8_t device_key[U2F_EC_KEY_SIZE];
uint8_t cert_key[U2F_EC_KEY_SIZE];
uint32_t counter;
const struct uECC_Curve_t* p_curve;
bool ready;
bool user_present;
U2fEvtCallback callback;
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);
return 1;
return 0;
}
U2fData* u2f_alloc() {
@@ -99,6 +114,7 @@ U2fData* u2f_alloc() {
void u2f_free(U2fData* U2F) {
furi_assert(U2F);
mbedtls_ecp_group_free(&U2F->group);
free(U2F);
}
@@ -129,8 +145,8 @@ bool u2f_init(U2fData* U2F) {
}
}
U2F->p_curve = uECC_secp256r1();
uECC_set_rng(u2f_uecc_random);
mbedtls_ecp_group_init(&U2F->group);
mbedtls_ecp_group_load(&U2F->group, MBEDTLS_ECP_DP_SECP256R1);
U2F->ready = true;
return true;
@@ -171,21 +187,63 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) {
der[0] = 0x30;
uint8_t len = 2;
len += u2f_der_encode_int(der + len, sig, 32);
len += u2f_der_encode_int(der + len, sig + 32, 32);
len += u2f_der_encode_int(der + len, sig, U2F_HASH_SIZE);
len += u2f_der_encode_int(der + len, sig + U2F_HASH_SIZE, U2F_HASH_SIZE);
der[1] = len - 2;
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) {
U2fRegisterReq* req = (U2fRegisterReq*)buf;
U2fRegisterResp* resp = (U2fRegisterResp*)buf;
U2fKeyHandle handle;
uint8_t private[32];
uint8_t private[U2F_EC_KEY_SIZE];
U2fPubKey pub_key;
uint8_t hash[32];
uint8_t signature[64];
uint8_t hash[U2F_HASH_SIZE];
uint8_t signature[U2F_EC_BIGNUM_SIZE * 2];
if(u2f_data_check(false) == false) {
U2F->ready = false;
@@ -201,40 +259,54 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
}
U2F->user_present = false;
hmac_sha256_context hmac_ctx;
sha256_context sha_ctx;
handle.len = U2F_HASH_SIZE * 2;
handle.len = 32 * 2;
// Generate random nonce
furi_hal_random_fill_buf(handle.nonce, 32);
// Generate private key
hmac_sha256_init(&hmac_ctx, U2F->device_key);
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
hmac_sha256_update(&hmac_ctx, handle.nonce, 32);
hmac_sha256_finish(&hmac_ctx, U2F->device_key, private);
{
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)));
// Generate private key handle
hmac_sha256_init(&hmac_ctx, U2F->device_key);
hmac_sha256_update(&hmac_ctx, private, 32);
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
hmac_sha256_finish(&hmac_ctx, U2F->device_key, handle.hash);
// Generate private key
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, handle.nonce, sizeof(handle.nonce)));
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, private));
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
pub_key.format = 0x04; // Uncompressed point
uECC_compute_public_key(private, pub_key.xy, U2F->p_curve);
u2f_ecc_compute_public_key(&U2F->group, private, &pub_key);
// Generate signature
uint8_t reserved_byte = 0;
sha256_start(&sha_ctx);
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);
{
uint8_t reserved_byte = 0;
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
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) {
U2fAuthReq* req = (U2fAuthReq*)buf;
U2fAuthResp* resp = (U2fAuthResp*)buf;
uint8_t priv_key[32];
uint8_t priv_key[U2F_EC_KEY_SIZE];
uint8_t mac_control[32];
hmac_sha256_context hmac_ctx;
sha256_context sha_ctx;
uint8_t flags = 0;
uint8_t hash[32];
uint8_t signature[64];
uint8_t hash[U2F_HASH_SIZE];
uint8_t signature[U2F_HASH_SIZE * 2];
uint32_t be_u2f_counter;
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);
// Generate hash
sha256_start(&sha_ctx);
sha256_update(&sha_ctx, req->app_id, 32);
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);
{
mbedtls_sha256_context sha_ctx;
// Recover private key
hmac_sha256_init(&hmac_ctx, U2F->device_key);
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);
mbedtls_sha256_init(&sha_ctx);
mbedtls_sha256_starts(&sha_ctx, 0);
// Generate and verify private key handle
hmac_sha256_init(&hmac_ctx, U2F->device_key);
hmac_sha256_update(&hmac_ctx, priv_key, 32);
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
hmac_sha256_finish(&hmac_ctx, U2F->device_key, mac_control);
mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id));
mbedtls_sha256_update(&sha_ctx, &flags, 1);
mbedtls_sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), sizeof(be_u2f_counter));
mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge));
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!");
memcpy(&buf[0], state_wrong_data, 2);
return 2;
@@ -311,7 +397,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
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->counter = be_u2f_counter;

View File

@@ -37,7 +37,7 @@ typedef struct {
uint32_t counter;
uint8_t random_salt[24];
uint32_t control;
} __attribute__((packed)) U2fCounterData;
} FURI_PACKED U2fCounterData;
bool u2f_data_check(bool cert_only) {
bool state = false;

View File

@@ -210,7 +210,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* 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);
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
}

View File

@@ -223,7 +223,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str);
*
* @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.
*

View File

@@ -499,6 +499,23 @@ VariableItem* variable_item_list_add(
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(
VariableItemList* variable_item_list,
VariableItemListEnterCallback callback,

View File

@@ -59,6 +59,15 @@ VariableItem* variable_item_list_add(
VariableItemChangeCallback change_callback,
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
*
* @param variable_item_list VariableItemList instance

View File

@@ -444,7 +444,7 @@ static bool notification_load_settings(NotificationApp* app) {
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
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) {
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);
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) {
fs_result = false;

View File

@@ -1,9 +1,10 @@
#include "flipper.pb.h"
#include "rpc_i.h"
#include "gui.pb.h"
#include <gui/gui_i.h>
#include <assets_icons.h>
#include <flipper.pb.h>
#include <gui.pb.h>
#define TAG "RpcGui"
typedef enum {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "rpc.h"
#include "storage/filesystem_api_defines.h"
#include <storage/filesystem_api_defines.h>
#include <pb.h>
#include <pb_decode.h>
#include <pb_encode.h>

View File

@@ -1,18 +1,18 @@
#include "flipper.pb.h"
#include <core/common_defines.h>
#include <core/memmgr.h>
#include <core/record.h>
#include "pb_decode.h"
#include "rpc/rpc.h"
#include "rpc_i.h"
#include "storage.pb.h"
#include "storage/filesystem_api_defines.h"
#include "storage/storage.h"
#include <stdint.h>
#include <rpc/rpc.h>
#include <rpc/rpc_i.h>
#include <storage/filesystem_api_defines.h>
#include <storage/storage.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/path.h>
#include <update_util/lfs_backup.h>
#include <pb_decode.h>
#include <storage.pb.h>
#include <flipper.pb.h>
#define TAG "RpcStorage"
#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) {
uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
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);
}

View File

@@ -165,6 +165,13 @@ typedef struct {
* @param total_space pointer to total space value
* @param free_space pointer to free space value
* @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 {
FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo);
@@ -175,6 +182,7 @@ typedef struct {
const char* fs_path,
uint64_t* total_space,
uint64_t* free_space);
bool (*const equivalent_path)(const char* path1, const char* path2);
} FS_Common_Api;
/** Full filesystem api structure */

View File

@@ -1,4 +1,9 @@
/**
* @file storage.h
* @brief APIs for working with storages, directories and files.
*/
#pragma once
#include <stdint.h>
#include "filesystem_api_defines.h"
#include "storage_sd_api.h"
@@ -23,43 +28,62 @@ extern "C" {
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);
/** 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);
/**
* @brief Enumeration of events emitted by the storage through the PubSub system.
*/
typedef enum {
StorageEventTypeCardMount,
StorageEventTypeCardUnmount,
StorageEventTypeCardMountError,
StorageEventTypeFileClose,
StorageEventTypeDirClose,
StorageEventTypeCardMount, /**< SD card was mounted. */
StorageEventTypeCardUnmount, /**< SD card was unmounted. */
StorageEventTypeCardMountError, /**< An error occurred during mounting of an SD card. */
StorageEventTypeFileClose, /**< A file was closed. */
StorageEventTypeDirClose, /**< A directory was closed. */
} StorageEventType;
/**
* @brief Storage event (passed to the PubSub callback).
*/
typedef struct {
StorageEventType type;
StorageEventType type; /**< Type of the event. */
} StorageEvent;
/**
* Get storage pubsub.
* @brief Get the storage pubsub instance.
*
* 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);
/******************* File Functions *******************/
/** Opens an existing file or create a new one.
* @param file pointer to file object.
* @param path path to file
* @param access_mode access mode from FS_AccessMode
/**
* @brief Open an existing file or create a new one.
*
* @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
* @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(
File* file,
@@ -67,202 +91,267 @@ bool storage_file_open(
FS_AccessMode access_mode,
FS_OpenMode open_mode);
/** Close the file.
* @param file pointer to a file object, the file object will be freed.
* @return success flag
/**
* @brief Close the file.
*
* @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);
/** Tells if the file is open
* @param file pointer to a file object
* @return bool true if file is open
/**
* @brief Check whether the 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);
/** Tells if the file is a directory
* @param file pointer to a file object
* @return bool true if file is a directory
/**
* @brief Check whether a file instance represents 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);
/** Reads bytes from a file into a buffer
* @param file pointer to file object.
* @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.
* @return uint16_t how many bytes were actually read
/**
* @brief Read bytes from a file into a buffer.
*
* @param file pointer to the file instance to read from.
* @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.
* @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.
* @return uint16_t how many bytes were actually written
/**
* @brief Write bytes from a buffer to a file.
*
* @param file pointer to the file instance to write into.
* @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.
* @param offset offset to move the r/w pointer
* @param from_start set an offset from the start or from the current position
/**
* @brief Change the current access position in a file.
*
* @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
*/
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.
* @return uint64_t position of the r/w pointer
/**
* @brief Get the current access position.
*
* @param file pointer to the file instance in question.
* @return current access position.
*/
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.
* @return bool success flag
/**
* @brief Truncate the file size to the current access position.
*
* @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);
/** Gets the size of the file
* @param file pointer to file object.
* @return uint64_t size of the file
/**
* @brief Get the file size.
*
* @param file pointer to the file instance in question.
* @return size of the file, in bytes.
*/
uint64_t storage_file_size(File* file);
/** Writes file cache to storage
* @param file pointer to file object.
* @return bool success flag
/**
* @brief Synchronise the file cache with the actual storage.
*
* @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);
/** Checks that the r/w pointer is at the end of the file
* @param file pointer to file object.
* @return bool success flag
/**
* @brief Check whether the current access position is at the end of the file.
*
* @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);
/**
* @brief Check that file exists
* @brief Check whether a file exists.
*
* @param storage
* @param path
* @return true if file exists
* @param storage pointer to a storage API instance.
* @param path pointer to a zero-terminated string containing the path to the file in question.
* @return true if the file exists, false otherwise.
*/
bool storage_file_exists(Storage* storage, const char* path);
/**
* @brief Copy data from one opened file to another opened file
* Size bytes will be copied from current position of source file to current position of destination file
* @brief Copy data from a source file to the destination file.
*
* @param source source file
* @param destination destination file
* @param size size of data to copy
* @return bool success flag
* Both files must be opened prior to calling this function.
*
* The requested amount of bytes will be copied from the current access position
* 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
* @param file pointer to file object.
* @param path path to directory
* @return bool success flag. You need to close the directory even if the open operation failed.
/**
* @brief Open a directory.
*
* Opening a directory is necessary to be able to read its contents with storage_dir_read().
*
* @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);
/** Close the directory. Also free file handle structure and point it to the NULL.
* @param file pointer to a file object.
* @return bool success flag
/**
* @brief Close the directory.
*
* @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);
/** Reads the next object in the directory
* @param file pointer to file object.
* @param fileinfo pointer to the read FileInfo, may be NULL
* @param name pointer to name buffer, may be NULL
* @param name_length name buffer length
* @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST)
/**
* @brief Get the next item in the directory.
*
* If the next object does not exist, this function returns false as well
* 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);
/** Rewinds the read pointer to first item in the directory
* @param file pointer to file object.
* @return bool success flag
/**
* @brief Change the access position to first item in the directory.
*
* @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);
/**
* @brief Check that dir exists
* @brief Check whether a directory exists.
*
* @param storage
* @param path
* @return bool
* @param storage pointer to a storage API instance.
* @param path pointer to a zero-terminated string containing the path of the directory in question.
* @return true if the directory exists, false otherwise.
*/
bool storage_dir_exists(Storage* storage, const char* path);
/******************* Common Functions *******************/
/** Retrieves unix timestamp of last access
/**
* @brief Get the last access time in UNIX format.
*
* @param storage The storage instance
* @param path path to file/directory
* @param timestamp the timestamp pointer
*
* @return FS_Error operation result
* @param storage pointer to a storage API instance.
* @param path pointer to a zero-terminated string containing the path of the item in question.
* @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.
*/
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
* @param path path to file/directory
* @param fileinfo pointer to the read FileInfo, may be NULL
* @return FS_Error operation result
/**
* @brief Get information about a file or a directory.
*
* @param storage pointer to a storage API instance.
* @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);
/** 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
* @param path
* @return FS_Error operation result
/**
* @brief Remove a file or a directory.
*
* 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);
/** Renames file/directory, file/directory must not be open. Will overwrite existing file.
* @param app pointer to the api
* @param old_path old path
* @param new_path new path
* @return FS_Error operation result
/**
* @brief Rename a file or a directory.
*
* The file or the directory must NOT be open.
* 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);
/** Copy file, file must not be open
* @param app pointer to the api
* @param old_path old path
* @param new_path new path
* @return FS_Error operation result
/**
* @brief Copy the file to a new location.
*
* The file must NOT be open at the time of calling this function.
*
* @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);
/** Copy one folder contents into another with rename of all conflicting files
* @param app pointer to the api
* @param old_path old path
* @param new_path new path
* @return FS_Error operation result
/**
* @brief Copy the contents of one directory into another and rename all conflicting files.
*
* @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 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);
/** Creates a directory
* @param app pointer to the api
* @param path directory path
* @return FS_Error operation result
/**
* @brief Create a directory.
*
* @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);
/** Gets general information about the storage
* @param app pointer to the api
* @param fs_path the path to the storage of interest
* @param total_space pointer to total space record, will be filled
* @param free_space pointer to free space record, will be filled
* @return FS_Error operation result
/**
* @brief Get the general information about the storage.
*
* @param storage pointer to a storage API instance.
* @param fs_path pointer to a zero-terminated string containing the path to the storage question.
* @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(
Storage* storage,
@@ -271,150 +360,242 @@ FS_Error storage_common_fs_info(
uint64_t* free_space);
/**
* @brief Parse aliases in path and replace them with real path
* Also will create special folders if they are not exist
* @brief Parse aliases in a path and replace them with the real path.
*
* @param storage
* @param path
* @return bool
* Necessary special directories will be created automatically if they did not exist.
*
* @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);
/**
* @brief Move content of one folder to another, with rename of all conflicting files.
* Source folder will be deleted if the migration is successful.
* @brief Move the contents of source folder to destination one and rename all conflicting files.
*
* @param storage
* @param source
* @param dest
* @return FS_Error
* Source folder will be deleted if the migration was successful.
*
* @param storage pointer to a storage API instance.
* @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);
/**
* @brief Check that file or dir exists
* @brief Check whether a file or a directory exists.
*
* @param storage
* @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 a file or a directory exists, false otherwise.
*/
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 *******************/
/** Retrieves the error text from the error id
* @param error_id error id
* @return const char* error text
/**
* @brief Get the textual description of a numeric error identifer.
*
* @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);
/** 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
* @return FS_Error error id
/**
* @brief Get the numeric error identifier from a file instance.
*
* @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);
/** 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
* @return FS_Error error id
/**
* @brief Get the internal (storage-specific) numeric error identifier from a file instance.
*
* @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);
/** 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
* @return const char* error text
/**
* @brief Get the textual description of a the last error associated with a file instance.
*
* @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);
/******************* SD Card Functions *******************/
/** Formats SD Card
* @param api pointer to the api
* @return FS_Error operation result
/**
* @brief Format 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_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.
* Will return FSE_DENIED if there are open files on the SD card.
* @param api pointer to the api
* @return FS_Error operation result
/**
* @brief Unmount the SD card.
*
* These return values have special meaning:
* - 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
* @return FS_Error operation result
/**
* @brief Mount the SD card.
*
* @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
* @param info pointer to the info
* @return FS_Error operation result
/**
* @brief Get SD card information.
*
* @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
* @return FS_Error operation result
/**
* @brief Get SD card status.
*
* @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 *******************/
typedef void (*Storage_name_converter)(FuriString*);
/** Backs up internal storage to a tar archive
* @param api pointer to the api
* @param dstname destination archive path
* @return FS_Error operation result
/**
* @brief Back up the internal storage contents to a *.tar archive.
*
* @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
* @param dstname archive path
* @param converter pointer to filename conversion function, may be NULL
* @return FS_Error operation result
/**
* @brief Restore the internal storage contents from a *.tar archive.
*
* @param storage pointer to a storage API instance.
* @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);
/***************** Simplified Functions ******************/
/**
* Removes a file/directory, the directory must be empty and the file/directory must not be open
* @param storage pointer to the api
* @param path
* @return true on success or if file/dir is not exist
* @brief Remove a file or a directory.
*
* The following conditions must be met:
* - 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);
/**
* Recursively removes a file/directory, the directory can be not empty
* @param storage pointer to the api
* @param path
* @return true on success or if file/dir is not exist
* @brief Recursively remove a file or a directory.
*
* Unlike storage_simply_remove(), the directory does not need to be empty.
*
* @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);
/**
* Creates a directory
* @param storage
* @param path
* @return true on success or if directory is already exist
* @brief Create a directory.
*
* @param storage pointer to a storage API instance.
* @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);
/**
* @brief Get next free filename.
* @brief Get the next free filename in a directory.
*
* @param storage
* @param dirname
* @param filename
* @param fileextension
* @param nextfilename return name
* @param max_len max len name
* Usage example:
* ```c
* FuriString* file_name = furi_string_alloc();
* Storage* storage = furi_record_open(RECORD_STORAGE);
*
* 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(
Storage* storage,

View File

@@ -198,15 +198,15 @@ static void storage_cli_read(Cli* cli, FuriString* path) {
File* file = storage_file_alloc(api);
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
const uint16_t buffer_size = 128;
uint16_t read_size = 0;
const size_t buffer_size = 128;
size_t read_size = 0;
uint8_t* data = malloc(buffer_size);
printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
do {
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]);
}
} while(read_size > 0);
@@ -227,7 +227,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
const uint16_t buffer_size = 512;
const size_t buffer_size = 512;
uint8_t* buffer = malloc(buffer_size);
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);
if(symbol == CliSymbolAsciiETX) {
uint16_t write_size = read_index % buffer_size;
size_t write_size = read_index % buffer_size;
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) {
storage_cli_print_error(storage_file_get_error(file));
@@ -257,7 +257,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
read_index++;
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) {
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)) {
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) {
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");
cli_getc(cli);
uint16_t read_size = storage_file_read(file, data, buffer_size);
for(uint16_t i = 0; i < read_size; i++) {
size_t read_size = storage_file_read(file, data, buffer_size);
for(size_t i = 0; i < read_size; i++) {
putchar(data[i]);
}
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);
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) {
storage_cli_print_error(storage_file_get_error(file));

View File

@@ -139,7 +139,7 @@ bool storage_file_close(File* file) {
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) {
return 0;
}
@@ -159,7 +159,8 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
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) {
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;
}
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) {
S_FILE_API_PROLOGUE;
S_API_PROLOGUE;
@@ -252,7 +287,7 @@ bool storage_file_exists(Storage* storage, const char* path) {
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);
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)) {
FuriString* dir_path = furi_string_alloc_set_str(old_path);
if(!furi_string_end_with_str(dir_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) {
// Cannot overwrite a file with a directory
if(storage_file_exists(storage, new_path)) {
error = FSE_INVALID_NAME;
furi_string_free(dir_path);
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)) {
@@ -742,6 +782,27 @@ bool storage_common_exists(Storage* storage, const char* path) {
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 ******************/
const char* storage_error_get_desc(FS_Error error_id) {

View File

@@ -69,6 +69,13 @@ typedef struct {
FuriThreadId thread_id;
} SADataCResolvePath;
typedef struct {
const char* path1;
const char* path2;
bool truncate;
FuriThreadId thread_id;
} SADataCEquivPath;
typedef struct {
uint32_t id;
} SADataError;
@@ -99,6 +106,7 @@ typedef union {
SADataCStat cstat;
SADataCFSInfo cfsinfo;
SADataCResolvePath cresolvepath;
SADataCEquivPath cequivpath;
SADataError error;
@@ -142,6 +150,7 @@ typedef enum {
StorageCommandSDStatus,
StorageCommandCommonResolvePath,
StorageCommandSDMount,
StorageCommandCommonEquivalentPath,
} StorageCommand;
typedef struct {

View File

@@ -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 *******************/
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);
do {
if(ret != FSE_OK) break;
if(storage_path_already_open(path, storage)) {
ret = FSE_ALREADY_OPEN;
break;
@@ -398,6 +406,31 @@ static FS_Error storage_process_common_fs_info(
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 ******************/
// TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage
#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);
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
case StorageCommandSDFormat:
message->return_data->error_value = storage_process_sd_format(app);

View File

@@ -596,6 +596,16 @@ static FS_Error storage_ext_common_fs_info(
#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 *******************/
static const FS_Api fs_api = {
.file =
@@ -624,6 +634,7 @@ static const FS_Api fs_api = {
.mkdir = storage_ext_common_mkdir,
.remove = storage_ext_common_remove,
.fs_info = storage_ext_common_fs_info,
.equivalent_path = storage_ext_common_equivalent_path,
},
};

View File

@@ -686,6 +686,10 @@ static FS_Error storage_int_common_fs_info(
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 *******************/
static const FS_Api fs_api = {
.file =
@@ -714,6 +718,7 @@ static const FS_Api fs_api = {
.mkdir = storage_int_common_mkdir,
.remove = storage_int_common_remove,
.fs_info = storage_int_common_fs_info,
.equivalent_path = storage_int_common_equivalent_path,
},
};

View File

@@ -44,7 +44,7 @@ static bool storage_settings_scene_bench_write(
}
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);
bool result = true;
*speed = -1;
@@ -82,7 +82,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
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_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};

View File

@@ -1,8 +1,8 @@
#include "storage_move_to_sd.h"
#include <core/common_defines.h>
#include <core/log.h>
#include "loader/loader.h"
#include <stdint.h>
#include <loader/loader.h>
#include <toolbox/dir_walk.h>
#include <toolbox/path.h>
#include <furi_hal.h>

View File

@@ -104,7 +104,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) {
update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0);
uint8_t* fw_block = malloc(FLASH_PAGE_SIZE);
uint16_t bytes_read = 0;
size_t bytes_read = 0;
uint32_t element_offs = 0;
while(element_offs < stack_size) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

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